Swift lazy instantiating using self
For some reason, a lazy property needs an explicit type annotation if its
initial value refers to self
. This is mentioned on the swift-evolution mailing list, however I cannot explain why that is
necessary.
With
lazy var first: FirstClass = FirstClass(second: self)
// ^^^^^^^^^^^^
your code compiles and runs as expected.
Here is another example which demonstrates that the problem occurs
also with struct
s, i.e. it is unrelated to subclassing:
func foo(x: Int) -> Int { return x + 1 }
struct MyClass {
let x = 1
lazy var y = foo(0) // No compiler error
lazy var z1 = foo(self.x) // error: use of unresolved identifier 'self'
lazy var z2: Int = foo(self.x) // No compiler error
}
The initial value of y
does not depend on self
and does not need a
type annotation. The initial values of z1/z2
depend on self
,
and it compiles only with an explicit type annotation.
Update: This has been fixed in Swift 4/Xcode 9 beta 3,
lazy property initializers can now reference instance members without explicit self
, and without explicit type annotation. (Thanks to @hamish for the update.)
Lazy initialisation and retain cycle
I tried this [...]
lazy var personalizedGreeting: String = { return self.name }()
it seems there are no retain cycles
Correct.
The reason is that the immediately applied closure {}()
is considered @noescape
. It does not retain the captured self
.
For reference: Joe Groff's tweet.
Lazy property initialization in Swift
It seems that this question has largely been answered, but to circle back to the original post, here is (IMHO) a relatively succinct translation in Swift. The key is that you can chain lazy properties. Note that I used both a class function and a closure - either is fine.
import Swift
println("begin")
class ClassWithLazyProperties {
lazy var entries:[String] = ClassWithLazyProperties.loadStuff()
lazy var entriesByNumber:Dictionary<Int, String> = {
var d = Dictionary<Int, String>()
for i in 0..<self.entries.count {
d[i] = self.entries[i]
}
return d
}()
private class func loadStuff() -> [String] {
return ["Acai", "Apples", "Apricots", "Avocado", "Ackee", "Bananas", "Bilberries"]
}
}
let c = ClassWithLazyProperties()
c.entriesByNumber
// 0: "Acai", 1: "Apples", 2: "Apricots", 3: "Avocado", 4: "Ackee", 5: "Bananas", 6: "Bilberries"]
println("end")
Initialize lazy instance variable with value that depends on other instance variables
You can use a once-only executed closure which captures properties of self
and use these at execution (= first use of the lazy
property). E.g.
class Foo {
var foo: Int
var bar: Int
lazy var lazyFoobarSum: Int = { return self.foo + self.bar }()
init(foo: Int, bar: Int) {
self.foo = foo
self.bar = bar
}
}
let foo = Foo(foo: 2, bar: 3)
foo.foo = 7
print(foo.lazyFoobarSum) // 10
W.r.t. to your own attempt: you may, in the same way, make use of help (instance) functions of self
in this once-only executed closure.
class Foo {
var foo: Int
var bar: Int
lazy var lazyFoobarSum: Int = { return self.getFooBarSum() }()
init(foo: Int, bar: Int) {
self.foo = foo
self.bar = bar
}
func getFooBarSum() -> Int { return foo + bar }
}
let foo = Foo(foo: 2, bar: 3)
foo.foo = 7
print(foo.lazyFoobarSum) // 10
How to create an immutable lazy property?
In short, that's not possible. As per Swift documentation:
Constant properties must always have a value before initialization
completes, and therefore cannot be declared as lazy.
Link: Swift lazy documentation
You can declare child
as private(set)
so it can only be changed from inside your class.
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.
`self.bounds.size.width` in lazy instantiation of UITableViewCell
Eric, try this way
public lazy var nameLabel: UILabel = {
return UILabel(frame: CGRect(x: 10, y: 0, width: self.bounds.size.width, height: 40))
}()
or
public lazy var nameLabel = UILabel(frame: CGRect(x: 10, y: 0, width: self.bounds.size.width, height: 40))
Related Topics
Precision String Format Specifier in Swift
How to Resolve "Ambiguous Use Of" Compile Error With Swift #Selector Syntax
Swift 5.0: 'Withunsafebytes' Is Deprecated: Use 'Withunsafebytes≪R≫(...)
Swiftui Hstack With Wrap and Dynamic Height
Remove or Edit User Location Blue Pulsing Circle
#Ifdef Replacement in the Swift Language
Do Swift-Based Applications Work on Os X 10.9/Ios 7 and Lower
The Use of Swift 3 @Objc Inference in Swift 4 Mode Is Deprecated
How to Update the Constant Height Constraint of a Uiview Programmatically
How to Make a Swiftui List Scroll Automatically
Instantiated Optional Variable Shows as Nil in Xcode Debugger
What Does "Fatal Error: Unexpectedly Found Nil While Unwrapping an Optional Value" Mean
Swift Generic Coercion Misunderstanding
How to Create Instances of Managed Object Subclasses in a Nsmanagedobject Swift Extension