Implicitly lazy static members in Swift
The static
property defines a "type property", one that is instantiated once and only once. As you note, this happens lazily, as statics behave like globals. And as The Swift Programming Language: Properties says:
Global constants and variables are always computed lazily, in a similar manner to Lazy Stored Properties. Unlike lazy stored properties, global constants and variables do not need to be marked with the
lazy
modifier.
This implicitly lazy behavior is because, as the Swift Blog: Files and Initialization says:
it allows custom initializers, startup time in Swift scales cleanly with no global initializers to slow it down, and the order of execution is completely predictable.
They consciously designed it that way to avoid unnecessarily delaying the startup of the app.
If you want to instantiate the static
property at some particular point in your app (rather than deferring it to where it's first used), simply reference this static
property at that earlier point and the object will be initialized at that time. Given the efforts we put into reducing the latency in starting our apps, you generally wouldn't to want this synchronously during the initial launch of the app, but you can do it wherever you want.
Note that unlike lazy
instance properties, the instantiating of globals and static
variables is thread-safe.
Setting lazy static variable first initializes then assigns?
You need lazy loading. Try this:
struct MyMap {
private static var _prop1: MyProtocol?
static var prop1: MyProtocol {
get { return _prop1 ?? MyClass() }
set(value) { _prop1 = value }
}
}
Or this:
struct MyMap {
private static var _prop1: MyProtocol?
static var prop1: MyProtocol {
get {
if _prop1 == nil {
_prop1 = MyClass()
}
return _prop1!
}
set(value) { _prop1 = value }
}
}
In Swift, why does assigning to a static variable also invoke its getter
This is because static and global stored variables are currently (this is all subject to change) only given one accessor by the compiler – unsafeMutableAddressor
, which gets a pointer to the variable's storage (this can be seen by examining the SIL or IR emitted).
This accessor:
Gets a pointer to a compiler-generated global flag determining whether the static variable has been initialised.
Calls
swift_once
with this pointer, along with a function that initialises the static variable (this is the initialiser expression you give it, i.e= Hat()
). On Apple platforms,swift_once
simply forwards ontodispatch_once_f
.Returns a pointer to the static variable's storage, which the caller is then free to read and mutate – as the storage has static lifetime.
So it does more or less the equivalent of the Objective-C thread-safe lazy initialisation pattern:
+(Hat*) hat {
static Hat* sharedHat = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedHat = [[Hat alloc] init];
});
return sharedHat;
}
The main difference being that Swift gives back a pointer to the storage of sharedHat
(a pointer to a reference), rather than sharedHat
itself (just a reference to the instance).
Because this is the one and only accessor for static and global stored variables, in order to perform an assignment, Swift needs to call it in order to get the pointer to the storage. Therefore, if it wasn't initialised already – the accessor needs to first initialise it to its default value (as it has no idea what the caller is going to do with it), before the caller then sets it to another value.
This behaviour is indeed somewhat unintuitive, and has been filed as a bug. As Jordan Rose says in the comments of the report:
This is currently by design, but it might be worth changing the design.
So this behaviour could well change in a future version of the language.
Why can I define a static constant that depends on another static constant in the same type, but not for properties?
Type and instance.
The type MyStruct always exists.
static
properties belong to the type. So they just sit there and can do anything they like or be related in any way. Okay, yes, it has to come into existence when the program starts, but under the hood the static property initializers are alllazy
so it's okay for one to depend on another (not in a circular way of course).An instance of MyStruct is a thing that has to be created, each time you say
MyStruct(...)
. When you say that, the instance properties must be initialized. An instance (notstatic
) property belongs to the instance. So its initializer's value cannot refer toself
becauseself
is exactly what we are in the middle of creating, i.e. the instance. This line:let myInstancePlusOne = myInstance + 1
...really means
let myInstancePlusOne = self.myInstance + 1
...but that is exactly what you are not allowed to say; at the time you are initializing this property there is no
self
yet, it is what you are initializing. And you can work around this by declaring that propertylazy
(with other adjustments in the syntax).
Correct use of lazy instantiation
Actually you've omitted (or elided into your third bullet point) the most common reason for lazy
instance properties: they can refer, explicitly or implicitly, to self
, whereas normal instance properties cannot.
Another point: lazy
instance properties do not have to be define-and-call anonymous functions, and in the silly example you give, there is no reason whatever for it to be so. This would have done just as well:
lazy var itemSize: CGSize = CGSize(width: 80, height: 80)
Both lazy
and define-and-call are useful, and are often useful together, but don't confuse them.
Re-initialize a lazy initialized variable in Swift
lazy is explicitly for one-time only initialization. The model you want to adopt is probably just an initialize-on-demand model:
var aClient:Client {
if(_aClient == nil) {
_aClient = Client(ClientSession.shared())
}
return _aClient!
}
var _aClient:Client?
Now whenever _aClient
is nil
, it will be initialized and returned. It can be reinitialized by setting _aClient = nil
Refer to 'self' in a static context
Lets look at a simpler (working) example keeping your core issue the same:
class ClassName {
static var bundle = Bundle(for: ClassName.self)
static func getBundle() -> Bundle {
return Bundle(for: self)
}
}
Firstly, lets note that Bundle(for: AnyClass)
takes an object type.
1. Regarding Variables
Variables at top-level, access self
as ClassName
which is an instance type, whether it is declared as let
/var
/lazy
/computed, static or not.
So:
static var bundle = Bundle(for: self)
is same as:
static var bundle = Bundle(for: ClassName())
Both are invalid and generate the following error:
Cannot convert value of type 'ClassName' to expected argument type 'AnyClass' (aka 'AnyObject.Type')
For sure, this is because we're passing an instance type instead of the expected Object type.
Solution:
static var bundle = Bundle(for: ClassName.self)
2. Regarding static functions
As for a static function, this is a bit different.
The metatype that you call the static method on is available to you in the method as
self
(it's simply passed as an implicit parameter).Ref: https://stackoverflow.com/a/42260880/2857130
In my example, we have:
static func getBundle() -> Bundle {
return Bundle(for: self)
}
When you call ClassName.getBundle()
, ClassName.Type
is implicitly passed to the function.
Now within the static function, self
is of type ClassName.Type
which is an Object type and can be applied directly in Bundle(for:)
, or similar functions that accept an Object type as it's parameter.
So, static functions access self
as ClassName.Type
which is same as ClassName.self
, just not apparent as it's passed implicitly.
You can confirm this behavior of self
in a static
function, and furthermore even observe how self
behaves in a normal function in the following example:
class ClassName {
static func check() {
print("static function check")
print(type(of: self)) //ClassName.Type
//same as
print(type(of: ClassName.self)) //ClassName.Type
//test
print(type(of: self) == type(of: ClassName.self)) //true
}
func check() {
print("normal function check")
print(type(of: self)) //ClassName
//test
print(type(of: self) == type(of: ClassName.self)) //false
}
}
ClassName.check()
ClassName().check()
Also shows us that normal functions access self
as ClassName
which is an instance type, similar to variables.
Summary:
Bundle(for:)
takes an object type- Variables at top-level, access
self
asClassName
which is an instance type - Normal functions access
self
asClassName
which is an instance type - Static functions access
self
asClassName.Type
which is an Object type, as it's passed implicitly to the function
Related Topics
How to Handle Closure Recursivity
Count Elements of Array Matching Condition in Swift
How to Test If Objects Conforming to the Same Protocol Are Identical in Swift Without Casting
Parse.Com Querying User Class (Swift)
Why I Can Change/Reassigned a Constant Value That Instantiated from a Class
Unexpectedly Large Realm File Size
Why Can't I Use a Tuple Constant as a Case in a Switch Statement
Converting a C-Style for Loop That Uses Division for the Step to Swift 3
Know When an Iteration Over Array with Async Method Is Finished
Is This a Good Way to Display Asynchronous Data
How to Write a Function That Will Unwrap a Generic Property in Swift Assuming It Is an Optional Type
Parameters After Opening Bracket
How to Convert Between Related Types Through a Common Initializer
Argument Labels Do Not Match Any Available Overloads
Swift Error: Missing Return in a Function Expected to Return 'String'
Autolayout Contraints for a View from Xib
Skphysicsbody Avoid Collision Swift/Spritekit
Extend Existing Protocols to Implement Another Protocol with Default Implements