Re-Initialize a Lazy Initialized Variable in Swift

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

iOS Swift4 - reInitialize lazy variable

its a lazy var clearly its a variable, so Swift will not stop you from modifying its value at any point in time if thats necessary.

You can simply say at any point,

guard let path = Bundle.main.path(
forResource: "Localizable",
ofType: "strings",
inDirectory: nil,
forLocalization: Localizer.shared.currentLanguage)
else {
fatalError("Localizable file NOT found")
}
self.localizableDictionary = NSDictionary(contentsOfFile: path)

FYI

Lazy initialization (also sometimes called lazy instantiation, or lazy
loading) is a technique for delaying the creation of an object or some
other expensive process until it’s needed. When programming for iOS,
this is helpful to make sure you utilize only the memory you need when
you need it.

above quote copied from http://mikebuss.com/2014/06/22/lazy-initialization-swift/

Please don't be under the impression that lazy var are constants if you really need a constant you will opt for let straight away :)

Hope it helps

When isn't necessary to initialize lazy variable with lambda execution?

People who do it the second way are making a mistake, that’s all.

The mistake is an easy one. Sometimes (1) a define-and-call initializer is necessary, namely when you need multiple code statements to obtain the initial value of a variable:

let timed : Bool = {
if val == 1 {
return true
} else {
return false
}
}()

Sometimes (2) you need lazy initialization, namely in order to mention self during property initialization:

lazy var arrow : UIImage = self.arrowImage()

And sometimes (3) you need both together to do both things:

lazy var prog : UIProgressView = {
let p = UIProgressView(progressViewStyle: .default)
p.alpha = 0.7
p.trackTintColor = UIColor.clear
p.progressTintColor = UIColor.black
p.frame = CGRect(x:0, y:0, width:self.view.bounds.size.width, height:20)
p.progress = 1.0
return p
}()

So it is natural out of habit, misunderstanding, or abundance of caution to resort to form 3 when in fact there was only one line and all you needed was form 2. It’s an easy mistake and does no harm.

Trigger lazy initializer again in Swift by setting property to nil

The lazy property initializer is responsible of initializing the property the first time it is accessed in read mode. Setting to nil has no effect on the initialization status - it's just a valid value the property stores.

You can mimic a lazy initialization with 3 properties:

  • a private initializer, implemented as a computed property (or a closure if you prefer)
  • a private backing property, storing the actual value
  • a non private property, which is the one you actually use in your code

The code looks like this:

class MyClass {
private var _myPropInitializer: Int {
return 5
}

private var _myProp: Int?

var myProp: Int? {
get {
if self._myProp == nil {
self._myProp = self._myPropInitializer
}
return _myProp!
}
set {
_myProp = newValue
}
}
}
  • the initializer property returns a computed value for the variable when it needs to be initialized, which is the 5 integer in the above example
  • myProp is an optional integer (to be able to store a nil):
    • on set, it will store the new value in the _myProp property
    • on get, if _myProp is nil, it invokes the initializer, assigning it to _myProp, and it returns its value

If you want to reuse that pattern, it's better to put everything in a class:

class Lazy<T> {
private let _initializer: () -> T
private var _value: T?
var value: T? {
get {
if self._value == nil {
self._value = self._initializer()
}
return self._value
}
set {
self._value = newValue
}
}

required init(initializer: () -> T) {
self._initializer = initializer
}
}

Note: a struct is not usable because setting a property inside a property getter is not allowed, whereas in a class it is.

Then you can use it as follows:

class MyTestClass {
var lazyProp: Lazy<Int>

init() {
self.lazyProp = Lazy( { return 5 } )
}
}

Some tests in playground:

var x = MyTestClass()
x.lazyProp.value // Prints {Some 5}
x.lazyProp.value = nil
x.lazyProp._value // Prints nil
x.lazyProp.value // Prints {Some 5}

The downside is that you have to access to the actual property as x.lazyProp.value and not as x.lazyProp.

Set lazy var from outside without initializing

The lazy initializer won't run if you just assign a value normally before ever reading. Here's a test case:

class Class {
lazy var lazyVar: String = {
print("Lazy initializer ran")
return "Default value"
}()
}

let object = Class()
object.lazyVar = "Custom value"
print(object.lazyVar)

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.

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


Related Topics



Leave a reply



Submit