Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Re-initialize a lazy initialized variable in Swift

Tags:

swift

I have a variable that initialized as:

lazy var aClient:Clinet = {     var _aClient = Clinet(ClinetSession.shared())     _aClient.delegate = self     return _aClient }() 

The problem is, at some point, I need to reset this aClient variable so it can initialize again when the ClinetSession.shared() changed. But if I set the class to optional Clinet?, LLVM will give me an error when I try to set it to nil. If I just reset it somewhere in the code using aClient = Clinet(ClinetSession.shared()), it will end up with EXEC_BAD_ACCESS.

Is there a way that can use lazy and being allowed to reset itself?

like image 574
Cai Avatar asked Aug 01 '14 03:08

Cai


People also ask

What is lazy initialization in Swift?

lazy initialisation is a delegation of object creation when the first time that object will be called. The reference will be created but the object will not be created. The object will only be created when the first time that object will be accessed and every next time the same reference will be used.

Can we declare lazy as static?

Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.

How do you declare a lazy variable in Swift?

You cannot declare a lazy variable anywhere in the code. To make a variable lazy, use the keyword modifier lazy in front of var. Instead of directly assigning a value to the lazy variable, it has to be computed. The computation takes place in a block of code.

Do you have to initialize variables in Swift?

All of a class's stored properties—including any properties the class inherits from its superclass—must be assigned an initial value during initialization. Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value.


2 Answers

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

like image 107
David Berry Avatar answered Sep 17 '22 08:09

David Berry


Because the behavior of lazy changed in Swift 4, I wrote a few structs that give very specific behavior, which should never change between language versions. I put these on GitHub, under the BH-1-PD license: https://github.com/RougeWare/Swift-Lazy-Patterns

ResettableLazy

Here is the one relevant to this question, which gives you a way to lazily-initialize a value, cache that value, and destroy it so it can be lazily-reinitialized later.

Note that this requires Swift 5.1! For the Swift 4 version, see version 1.1.1 of that repo.

The simple usage of this is very straightforward:

@ResettableLazy var myLazyString = "Hello, lazy!"  print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!" print(myLazyString) // Just returns the value "Hello, lazy!"  _myLazyString.clear() print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!" print(myLazyString) // Just returns the value "Hello, lazy!"  myLazyString = "Overwritten" print(myLazyString) // Just returns the value "Overwritten" _myLazyString.clear() print(myLazyString.wrappedValue) // Initializes, caches, and returns the value  "Hello, lazy!" 

This will print:

Hello, lazy! Hello, lazy! Hello, lazy! Hello, lazy! Overwritten Hello, lazy! 

If you have complex initializer logic, you can pass that to the property wrapper:

func makeLazyString() -> String {     print("Initializer side-effect")     return "Hello, lazy!" }  @ResettableLazy(initializer: makeLazyString) var myLazyString: String  print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!" print(myLazyString) // Just returns the value "Hello, lazy!"  _myLazyString.clear() print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!" print(myLazyString) // Just returns the value "Hello, lazy!"  myLazyString = "Overwritten" print(myLazyString) // Just returns the value "Overwritten" _myLazyString.clear() print(myLazyString.wrappedValue) // Initializes, caches, and returns the value  "Hello, lazy!" 

You can also use it directly (instaed of as a property wrapper):

var myLazyString = ResettableLazy<String>() {     print("Initializer side-effect")     return "Hello, lazy!" }  print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!" print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"  myLazyString.clear() print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!" print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"  myLazyString.wrappedValue = "Overwritten" print(myLazyString.wrappedValue) // Just returns the value "Overwritten" _myLazyString.clear() print(myLazyString.wrappedValue) // Initializes, caches, and returns the value  "Hello, lazy!" 

These will both print:

Initializer side-effect Hello, lazy! Hello, lazy! Initializer side-effect Hello, lazy! Hello, lazy! Overwritten Initializer side-effect Hello, lazy! 

This answer has been updated; its original solution no longer works in Swift 4 and newer.

Instead, I recommend you use one of the solutions listed above, or @PBosman's solution

Previously, this answer hinged on behavior which was a bug. Both that old version of this answer, its behavior, and why it's a bug are described in the text and comments of Swift bug SR-5172 (which has been resolved as of 2017-07-14 with PR #10,911), and it's clear that this behavior was never intentional.

That solution is in that Swift bug's text, and also in the history of this answer, but because it's a bug exploit that doesn't work in Swift 3.2+ I recommend you do not do that.

like image 23
Ky. Avatar answered Sep 21 '22 08:09

Ky.