4

I've created some sample project to test out various types variable implementations to test out which ones are executed only once and which ones are executed every time called

class Something:NSObject
{
    var clock:Int = 0
    override var description: String
    {
        let desc = super.description
        clock += 1
        return "\(desc) Clock: \(clock)"
    }
}

static var staticVar:Something
{
    print("static Var")
    return Something()
}
static var staticVar2:Something = {
    print("static Var II")
    return Something()
}()

lazy var lazyVar:Something = {
    print("lazy Var")
    return Something()
}()

var simpleVar:Something {
    print("simple Var")
    return Something()
}

var simpleVar2:Something = {
    print("simple Var II")
    return Something()
}()

then in viewDidLoad()(to make sure variables are already initialized), called all the vars few times and saved in array to keep references strong

var strongArr = [Something]()

print("== STATIC VAR")
strongArr.append(ViewController.staticVar)
print(strongArr.last!.description)
strongArr.append(ViewController.staticVar)
print(strongArr.last!.description)
strongArr.append(ViewController.staticVar)
print(strongArr.last!.description)

print("\n== STATIC VAR {}()")
strongArr.append(ViewController.staticVar2)
print(strongArr.last!.description)
strongArr.append(ViewController.staticVar2)
print(strongArr.last!.description)
strongArr.append(ViewController.staticVar2)
print(strongArr.last!.description)

print("\n== SIMPLE VAR")
strongArr.append(self.simpleVar)
print(strongArr.last!.description)
strongArr.append(self.simpleVar)
print(strongArr.last!.description)
strongArr.append(self.simpleVar)
print(strongArr.last!.description)

print("\n== SIMPLE VAR {}()")
strongArr.append(self.simpleVar2)
print(strongArr.last!.description)
strongArr.append(self.simpleVar2)
print(strongArr.last!.description)
strongArr.append(self.simpleVar2)
print(strongArr.last!.description)

print("\n== LAZY VAR {}()")
strongArr.append(self.lazyVar)
print(strongArr.last!.description)
strongArr.append(self.lazyVar)
print(strongArr.last!.description)
strongArr.append(self.lazyVar)
print(strongArr.last!.description)

This is the result logged out in Console

== STATIC VAR
static Var
<_TtCC8DemoDemo14ViewController9Something: 0x600003725100> Clock: 1
static Var
<_TtCC8DemoDemo14ViewController9Something: 0x600003725160> Clock: 1
static Var
<_TtCC8DemoDemo14ViewController9Something: 0x600003725270> Clock: 1

== STATIC VAR {}()
static Var II
<_TtCC8DemoDemo14ViewController9Something: 0x6000037251b0> Clock: 1
<_TtCC8DemoDemo14ViewController9Something: 0x6000037251b0> Clock: 2
<_TtCC8DemoDemo14ViewController9Something: 0x6000037251b0> Clock: 3

== SIMPLE VAR
simple Var
<_TtCC8DemoDemo14ViewController9Something: 0x600003725240> Clock: 1
simple Var
<_TtCC8DemoDemo14ViewController9Something: 0x6000037252a0> Clock: 1
simple Var
<_TtCC8DemoDemo14ViewController9Something: 0x6000037252b0> Clock: 1

== SIMPLE VAR {}()
<_TtCC8DemoDemo14ViewController9Something: 0x600003738100> Clock: 1
<_TtCC8DemoDemo14ViewController9Something: 0x600003738100> Clock: 2
<_TtCC8DemoDemo14ViewController9Something: 0x600003738100> Clock: 3

== LAZY VAR {}()
lazy Var
<_TtCC8DemoDemo14ViewController9Something: 0x60000372ea70> Clock: 1
<_TtCC8DemoDemo14ViewController9Something: 0x60000372ea70> Clock: 2
<_TtCC8DemoDemo14ViewController9Something: 0x60000372ea70> Clock: 3

Based on these tests, looks like there is no difference between lazy var and simple var if both of them are defined as closures (() at the end).

Does variable implementation as closure automatically make a variable lazy or am I missing something?

2
  • What is the Clock variable supposed to signify? The amount of times that the description method is called? Commented Oct 16, 2018 at 2:55
  • @brett, it is to make sure the same object is being called when clock increases. I had some problems testing before introduced strong referencing with array - same addresses were showing same clock number meaning different objects were allocated on same address spot (once object was released, another took it's memory space even running on same thread as you can see from sample project above). Commented Oct 16, 2018 at 15:30

2 Answers 2

2

The difference is when the init code for the variable is run. For lazy vars, the init code is run on first access of that variable. For non-lazy vars, it's run when the struct/class is initialized.

struct N {
    lazy var a: Int = { print("Setting A"); return 5}();
    var b: Int = { print("Setting B"); return 5 }()
}

var n = N()
print(n.a)
print(n.b)

Output:

Setting B
Setting A
5
5

Note how non-lazy b is initialized first. a is only initialized when it's accessed. In either case, the initializer for each property is only run once.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! Somehow I missed first line of log which showed exact same thing you are talking here - var-as-a-closure was initiated together with parent class and lazy is meant for memory saving to make sure it is allocated only if needed
2

They get more interresting when you mix them up with other properties of the struct / class. Here are a couple that I can think of:

var-as-closure cannot reference other instance variables

struct Person {
    var firstName: String
    var lastName: String

    lazy var fullName1 = "\(firstName) \(lastName)"             // OK
    var fullName2: String = { "\(firstName) \(lastName)" }()    // invalid

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
}

The reason is that var-as-closure is evaluated during initialization and Swift does not guarantee which property will be initialized first. firstName and lastName may not have been initialized yet when fullName2 is initalized.

You cannot define a constant instance of the struct containing lazy var

let p = Person(firstName: "John", lastName: "Smith")
print(p.fullName1)                                      // runtime error

A lazy var is calculated the first time you read it so by definition, it mutates the struct. Hence let p = Person(...) is invalid. You must use var p = Person(...).

However, if Person is a class, you can use let p = Person(...) since "constant" here means p points to a fixed memory address, but the object at that address can change anytime.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.