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 anil
):- on set, it will store the new value in the
_myProp
property - on get, if
_myProp
isnil
, it invokes the initializer, assigning it to_myProp
, and it returns its value
- on set, it will store the new value in the
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
Type Conversion When Using Protocol in Swift
Swift Optional Inout Parameters and Nil
How String Comparison Happens in Swift
Select() from Linq in Swift 3.0
Convert Avaudiopcmbuffer to Nsdata and Back
Remove Default Padding from List in Swiftui
Initialize Class-Instance and Access Variables in Swift
How to Set a New Root View Controller
Referring to Environment Variables in Swift
Wkwebview Does Not Load Links to Pdfs
How to Use the Appropriate Color Class For the Current Platform
Swiftui - Passing Data from Swiftuiview to Scenekit
How to Left Align the Title of a Navigation Bar in Xcode
What Causes 'Constant Captured by a Closure Before Being Initialized' Error
What Is Preventing My Conversion from String to Int When Decoding Using Swift 4's Codable