Swift Lazy and Optional Properties

Swift Lazy and Optional properties

OK, this is an interesting question, and I don't want to imply that the existing answers aren't good, but I thought I'd offer my take on things.

lazy variables are great for things that need to be setup once, then never re-set. It's a variable, so you could change it to be something else, but that kind of defeats the purpose of a lazy variable (which is to set itself up upon demand).

Optionals are more for things that might go away (and might come back again). They need to be set up each time.

So let's look at two scenarios for your side menu: one where it stays around while it's not visible, and another for when it is deallocated.

lazy var sideMenu = SideMenu()

So the first time the sideMenu property is accessed, SideMenu() is called and it is assigned to the property. The instance stays around forever, even when you're not using it.

Now let's see another approach.

var _sideMenu: SideMenu?
var sideMenu: SideMenu! {
get {
if let sm = _sideMenu {
return sm
} else {
let sm = SideMenu()
_sideMenu = sm
return sm
}
}
set(newValue) {
_sideMenu = newValue
}
}

(Note this only works for classes, not structs.)

OK so what does this do? Well it behaves very similarly to the lazy var, but it let's you re-set it to nil. So if you try to access sideMenu, you are guaranteed to get an instance (either the one that was stored in _sideMenu or a new one). This is a similar pattern in that it lazily loads SideMenu() but this one can create many SideMenu() instances, where the previous example can only create one once.

Now, most view controllers are small enough that you should probably just use lazy from earlier.

So two different approaches to the same problem. Both have benefits and drawbacks, and work better or worse in different situations.

Lazy loading optional property that maybe nil later

Besides what already suggested in this answer, the most elegant alternate solution I can propose is to intercept when the property is set to nil, and override the assignment with a default value:

class SomeClass {
private var _optionalVar: String?

var nonoptionalVar: String? {
get { return _optionalVar }
set { _optionalVar = newValue == nil ? "123" : newValue }
}

init() {
self.nonoptionalVar = nil
}
}

There is one downside though: you have to remember to explicitly set the property to nil in the initializer (otherwise the property will be left at its default initial value, which is nil)

More elegant way to assign optionals to a lazy property

One way is to use an operator like this:

infix operator ?=>
func ?=>(lhs: Any?, rhs: @autoclosure ()->()) {
guard lhs != nil else { return }
rhs()
}

Usage:

image ?=> (imageView1.image = image)

I don't know if we can combine the first and last argument since they are alwayse the same.



Update - Option 2

Following @Sweeper's comment:

func ?=><T>(lhs: T?, rhs: (T)->()) {
guard let lhs = lhs else { return }
rhs(lhs)
}

Usage:

someOptional?.someChildOptional?.image = { imageView2.image = $0 }

It's better for long optional changings but yet requires curly braces and $ argument (that I forgot it's name)

Swift must use lazy for stored property when use self in closure?

When you are declaring label with lazy initial value is not calculated until the first time it is used.
so the probably the Instantiates views was completed.

But the concept of using let in Swift, variables which are let have to be initialized before you can use self.

Using lazy var means that the compiler can verify that the value assigned to label won't be accessed before self is a valid object, because it won't be possible to call label until all other members of the class have been initialized.

Swift Lazy Properties in More Detail

In the apple's example,

lazy var managedObjectModel: NSManagedObjectModel =
{
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("FLO_Cycling_1_1_1", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()

You can see that, apple implicitly specified the variable type:

lazy var managedObjectModel: NSManagedObjectModel

and returning a NSManagedObjectModel from that code itself.

In your first example, you are not specifying the type of that variable, also not returning any value (assigning or initializing). So the compiler complains that you need to specify a type implicitly and need to initialize it.

The basic idea is, you need to specify it's type implicitly and also you need to initialize it (writing just a println, doesn't initialize it. So in your scenario. You don't have any value for that particular lazy variable. So you need to specify it's type as a empty closure and you are initializing it with that.

Another example, The following will assign 7 to the check:

lazy var check : Int = {
println("This is your check")
return 7
}()

The difference between Swift Optional Property Declarations

var optionalProperty1: Type!

When you're sure you will have value for this property such as timestamp it will be something for sure. And Yes it can be nil too.

var optionalProperty2: Type?

When you're not sure about the value (Or this field is not mandatory) take it as optional for example:- If I make a Person class address can be optional and name will not.

lazy var optionalProperty3: Type

This syntax is wrong you can not declare lazy property in this way. You must assign something to it initially. See below example:

/// First way
lazy var optionalProperty3: String = {
return "Hello"
}()

/// Second way
lazy var optionalProperty4 = "Hello"

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 variables are great for things that need to be setup once, then never re-set.

One more thing you really don't need to specify type in modern Swift. Means if you will assign 0 it will be an integer itself, if you will assign 0.0 it will take it as double and same for String, Array etc.

Swift's Lazy Var

In your examples the outcomes are not quite the same, in the following ways:

  1. Single instantiation. Each time buttonPressed() is called, a new HeavyClass will be instantiated. This is not the case when using the lazy keyword, which will only create the instance on first access. To match the lazy semantics you would have to check and set if heavyClass == nil before each access.

  2. Nullability. You must unwrap your heavyClass each time you want to use it, either through optional chaining (heavyClass?.doStuff()) or force unwrapping (heavyClass!.doStuff()). You are also able to set the variable back to nil, which would be a compiler error in the first example.

The real wins of lazy variables are when you have multiple places which use the variable. I'm sure you can spot the repetition here:

func buttonPressed() {
if self.heavyClass == nil {
self.heavyClass = HeavyClass()
}
self.heavyClass?.doStuff()
}

func aDifferentButtonPressed() {
if self.heavyClass == nil {
self.heavyClass = HeavyClass()
}
self.heavyClass?.doSomethingElse()
}

This is tidied up using a lazy variable:

func buttonPressed() {
self.heavyClass.doStuff()
}

func aDifferentButtonPressed() {
self.heavyClass.doSomethingElse()
}

Swift: why lazy, computed property, and property observer can not be let

Lazy properties : You must always declare a lazy property as a variable (with the var keyword), because its initial value might not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.

computed property : whereas computed properties calculate (rather than store) a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.

property observer : property observers is to monitor changes in a property’s value, if you define it let then how you can monitor changes because let is one type of constant which you can not change after init.



Related Topics



Leave a reply



Submit