Lazy Property Initialization in Swift

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")

In Swift, do we need to initialize lazy stored properties?

No you do not:

A lazy stored property is a property whose initial value is not calculated until the first time it is used...

You must always declare a lazy property as a variable (with the var
keyword), because its initial value may not be retrieved until after
instance initialization completes...

Lazy properties are useful when the initial value for a property is
dependent on outside factors whose values are not known until after an
instance’s initialization is complete.

Source

Basically meaning that it does not have a value and does not need one immediately after initialization.

How to use self in lazy initialization in Swift

Your question is written under the misunderstanding that you are currently using lazy initialization. But you are not. Both label1 and label2 are not using lazy initialization. They are being initialized immediately when A is being initialized and this is the cause of the error since self isn't ready when these property initializers are called.

The solution is to actually make label2 a lazy property.

private lazy var label2: UILabel = {
let view = UILabel()
self.addTextToLabel(label: view)
return view
}()

But note that this label2 initialization will not happen until the first time you actually try to access the label2 property.

As per the docs:

A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.

Lazy properties are useful when the initial value for a property is dependent on outside factors whose values are not known until after an instance’s initialization is complete.

Reference:
https://docs.swift.org/swift-book/LanguageGuide/Properties.html

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.

Initialization of properties in a class, lazy properties in specific?


  1. Yep, that is correct.
  2. Yep, that is also correct.
  3. What is being initialized is the constant someClass. Declaration is the introduction of a new named value into your program. You declare the name of the constant (or variable) and identify its type like this:

    let someClass: SomeClassWithLazyVar

But at that point it still hasn't been initialized. You initialize the constant by assigning it a value:

someClass = SomeClassWithLazyVar()

The vast majority of the time (especially with constants) you declare the constant and initialize it at the same time:

let someClass = SomeClassWithLazyVar()

Whether or not you need to pass arguments inside the parentheses depends on the initializer for the object that you are creating. I'm assuming that SomeClassWithLazyVar has an initializer that takes no arguments, like this:

init() { }

What are the potential repercussions of a lazy property getting initialised more than once?

(See my comment to rmaddy's answer regarding my concern about thread-safety on writing the pointer itself. My gut is that memory corruption is not possible, but that object duplication is. But I can't prove so far from the documentation that memory corruption isn't possible.)

Object duplication is a major concern IMO if the lazy var has reference semantics. Two racing threads can get different instances:

  • Thread 1 begins to initialize (object A)
  • Thread 2 begins to initialize (object B)
  • Thread 1 assigns A to var and returns A to caller
  • Thread 2 assigns B to var and returns B to caller

This means that thread 1 and thread 2 have different instances. That definitely could be a problem if they are expecting to have the same instance. If the type has value semantics, then this shouldn't matter (that being the point of value semantics). But if it has reference semantics, then this very likely be a problem.

IMO, lazy should always be avoided if multi-threaded callers are possible. It throws uncertainty into what thread the object construction will occur on, and the last thing you want in a thread-safe object is uncertainty about what thread code will run on.

Personally I've rarely seen good use cases for lazy except for where you need to pass self in the initializer of one of your own properties. (Even then, I typically use ! types rather than lazy.) In this way, lazy is really just a kludgy work-around a Swift init headache that I wish we could solve another way, and do away with lazy, which IMO has the same "doesn't quite deliver what it promises, and so you probably have to write your own version anyway" problem as @atomic in ObjC.

The concept of "lazy initialization" is only useful if the type in question is both very expensive to construct, and unlikely to ever be used. If the variable is actually used at some point, it's slower and has less deterministic performance to make it lazy, plus it forces you to make it var when it is most often readonly.

How to re-initialize a lazy property from another view?

I would suggest creating a function that loads the data into data and then whenever you need to reload data, simply reassign it.

class DataStore {
lazy var data: [String: String] = loadData()

func readFromDisk() -> Data? {...}

func processData(data: Data) -> [String:String] { ... }

func loadData() -> [String:String] {
guard let data = readFromDisk() else { return [:] }
return processData(data: data)
}
}

let store = DataStore()
let data = store.data // only loaded here
store.data = store.loadData() // reloads the data

If you don't want the loadData function to be exposed, you can also create a separate reloadData function.

class DataStore {
...
func reloadData() {
data = loadData()
}
}

and then instead of doing store.data = store.loadData(), simply call store.reloadData()

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 stored property in Swift

Mike Buss wrote an article about lazy initialization in Swift http://mikebuss.com/2014/06/22/lazy-initialization-swift/

One example of when to use lazy initialization is when the initial value for a property is not known until after the object is initialized or when the computation of the property is computationally expensive.

Here are two examples for both cases from the post:

In the first example we don't know which value personalized greeting should have. We need to wait until the object is initialized to know the correct greeting for this person.

class Person {

var name: String

lazy var personalizedGreeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()

init(name: String) {
self.name = name
}
}

The second example covers the case of an expensive computation. Imagine a MathHelper class which should give you values for pi and other important constants. You don't need to calculate all constants if you only use a subset of them.

class MathHelper {

lazy var pi: Double = {
// Calculate pi to a crazy number of digits
return resultOfCalculation
}()

}

Swift static property initilizers are lazy why I could declared it as a constant

Neuburg M. is drawing a distinction between static properties and instance properties. You are pretending to ignore that distinction. But you cannot ignore it; they are totally different things, used for different purposes.

In this code:

class Person { // let's declare a static property
static let firstNaID = "First Name"
}

... firstNaID is already lazy. But now try to do this:

class Person { // let's declare an instance property
lazy let firstNaID : String = "First Name" // error
}

You can't; as things stand (up thru Swift 3.1), you have to say lazy var instead — and when you do, you get a lazy instance property.

Your static let declaration thus doesn't accomplish what lazy let wanted to accomplish, because a static property is not an instance property.



Related Topics



Leave a reply



Submit