Swift Lazy Instantiating Using Self

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 structs, 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



Leave a reply



Submit