Lazy and Static Variables

Lazy Variables

This is an advanced and niche variable type that can help tidy up your code, but you can get by without understanding it.

Imagine you have some class with a top-level variable declaration:

class MyClass {

    let myVariable = <insert expression>

}

Whenever that class is initialized, the expression will be evaluated to a value, and that value will be bound to the identifier myVariable. This is great unless that expression relies on self. Since this computation is occurring at initialization, self is not accessible because it is not done being created. It may be missing fields, properties, whatever. It is not safe to use, so the compiler will get mad at you for using self before it is accessible.

When designing UI, this can often result from trying to set a delegate to self (myVariable.delegate = self), but it can also occur anytime you try to access another field or method in the expression to be evaluated. You can solve this with assignment in the initializer:

class MyClass {
    
    let myVariable: someType
    
    init(...) {
        myVariable = <expression>
        ...
    }
}

You can also solve this by making the variable type optional var myVariable: someType? (or an implicitly unwrapped optional var myVariable: someType!) and assigning it in a function that will be called in the class's normal lifecycle–maybe viewDidLoad.

Even better than using the latter technique would be to assign an expression and then update it later with the proper fields/methods, so you can avoid optionals entirely. Let's say you are using a view that needs a delegate. If the delegate can be assigned to the view after initialization (which is normally the case) you can create the initial view on init, and then update it with self later.

class MyClass: MyViewDelegate {

    let myVariable = MyView()
    
    init(...) {
        <all initialization work>
        myVariable.delegate = self
    }

}

These are all valid solutions that people use frequently. For stylistic reasons, you may be dissuaded from such techniques–it's not ideal to spread your variable setup across places if possible. Equally valid, you may not like to pollute your init or whatever function with variable setup.

Okay, so how would I fix this? The original problem is that you are trying to access self before it is ready to access... so don't! We can declare a variable (not a constant) to be lazy, which means that it will only be evaluated when it is needed–the first time it is referenced. This means that the variable will always be ready when we ask for it (no risks of optionals not being assigned) at the cost of some computation occurring later in the class's life cycle instead of on initialization–but this is not going to be noticeable unless you are doing some heavy computation.

class MyClass: MyViewDelegate {

    lazy var myVariable = MyView(delegate: self)

}

Often when using Apple's UIKit framework, you can initialize the view, and self does not show up until you are trying to configure your view. This lends itself well to the technique of initializing the view and doing setup later in the init function or some other function that you can guarantee will be called before you need the variable. But again, this has the issue of spreading code out and potentially polluting functions with extra setup. We can refactor such code by creating a closure block (anonymous function) that will setup up the object and then return it–see more about anonymous functions on the Functions page.

There are a few things to note with this technique:

  1. If you create a new variable in the closure, it must have a different name than the lazy variable that it will be assigned to because the compiler will get mad about referencing a variable that has yet to be initialized–just like what was happening with self before!

  2. If you are using a closure, you will need to give the variable an explicit type because the compiler is not good at figuring out types for closures

  3. You must call the function that returns the value

class MyClass: MyViewDelegate {
    
    lazy var myVariable: MyView = {
        let view = MyView()
        view.delegate = self
        return view
    }()
    
}

Static Variables

When creating classes and structs, it may make sense to store values and functions within the overarching data type and not the objects (store them in the original class or struct instead of the objects they create). One special example of this is the init function. It makes no sense for it to be stored in the object because it would require an object to make another one–there would also be no first instance, so there would be no instances ever. This is why we can do <ClassName>.init or <StructName>.init. Of course, there are other things that we may want to access in a similar manner. To do this for, variables, constants, and functions, you do everything as you normally would but with the static keyword beforehand.

One common use of static functions is infix functions. These functions let us do comparisons like equality in our code without doing ugly things like accessing methods. Using our implementation of Fraction with fields numerator: Int, denominator: Int and computed property decimal: Float (defined in the Variable Properties page) we can write a function for equality:

extension Fraction {

    static func == (_ lhs: Fraction, _ rhs: Fraction) -> Bool {
        return lhs.decimal == rhs.decimal
    }

}

It is convention for infix operators to use anonymous parameters and the lhs (left-hand side) rhs (right-hand side) notation for Swift, but these can be replaced with whatever you want.

You'll also find static variables in the wild quite a lot. You may have noticed when using UIColor or NSColor, if a variable has one of those as its type, you can just write .red, .blue, or some other built-in color. This is because of static variables! Since the compiler knows the type, if you start the expression with ., the compiler will assume you are looking within that type, and what's in that type, well everything that's stored in the class. This works for Enums too!

Last updated