Разница между Lazy var и var как закрытие в Swift

Я создал пример проекта, чтобы протестировать реализации переменных различных типов, чтобы проверить, какие из них выполняются только один раз, а какие выполняются каждый раз при вызове.

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()
}()

затем в viewDidLoad() (чтобы убедиться, что переменные уже инициализированы), вызывал все переменные несколько раз и сохранял в массиве, чтобы сохранить сильные ссылки

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)

Это результат выхода из консоли

== 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

Основываясь на этих тестах, похоже, что нет никакой разницы между lazy var и simple var, если они оба определены как замыкания (() в конце).

Реализация переменной как закрытия автоматически делает переменную ленивой или я что-то упустил?


person Paulius Vindzigelskis    schedule 15.10.2018    source источник
comment
Что должна означать переменная Clock? Сколько раз вызывается метод описания?   -  person Brett    schedule 16.10.2018
comment
@brett, это нужно, чтобы при увеличении часов вызывался один и тот же объект. У меня были некоторые проблемы с тестированием до того, как были введены строгие ссылки с массивом - одни и те же адреса показывали один и тот же номер часов, что означает, что разные объекты были размещены в одном и том же адресном месте (как только объект был освобожден, другой занял его место в памяти, даже работая в том же потоке, как вы можете видеть из примера проект выше).   -  person Paulius Vindzigelskis    schedule 16.10.2018


Ответы (2)


Разница заключается в том, когда запускается код инициализации для переменной. Для lazy переменных код инициализации запускается при первом доступе к этой переменной. Для переменных non-lazy он запускается при инициализации структуры/класса.

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)

Выход:

Setting B
Setting A
5
5

Обратите внимание, что сначала инициализируется неленивый b. a инициализируется только при доступе к нему. В любом случае инициализатор для каждого свойства запускается только один раз.

person Kon    schedule 16.10.2018
comment
Спасибо! Почему-то я пропустил первую строку журнала, которая показала то же самое, о чем вы здесь говорите - var-as-a-closure был инициирован вместе с родительским классом, а lazy предназначен для сохранения памяти, чтобы убедиться, что она выделяется только при необходимости - person Paulius Vindzigelskis; 16.10.2018

Они становятся более интересными, когда вы смешиваете их с другими свойствами структуры/класса. Вот пара, о которой я могу думать:

var-as-close не может ссылаться на другие переменные экземпляра

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
    }
}

Причина в том, что var-as-closure оценивается во время инициализации, а Swift не гарантирует, какое свойство будет инициализировано первым. firstName и lastName, возможно, еще не были инициализированы, когда инициализируется fullName2.

Вы не можете определить постоянный экземпляр структуры, содержащей lazy var

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

lazy var вычисляется при первом чтении, поэтому по определению он изменяет структуру. Следовательно, let p = Person(...) недействителен. Вы должны использовать var p = Person(...).

Однако, если Person является классом, вы можете использовать let p = Person(...), поскольку "константа" здесь означает, что p указывает на фиксированный адрес памяти, но объект по этому адресу может измениться в любое время.

person Code Different    schedule 16.10.2018