Swift中Lazy var和var作为闭包的区别

3

我创建了一些样例项目来测试各种类型的变量实现,以测试哪些变量只执行一次,哪些变量每次调用都执行。

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

根据这些测试结果,看起来如果两者都定义为闭包(在结尾处加上()),那么懒惰变量和简单变量之间没有区别。

将变量实现为闭包是否自动使变量成为懒惰变量,还是我漏掉了什么?


时钟变量应该表示什么?是描述方法被调用的次数吗? - Brett
@brett,这是为了确保在时钟增加时调用相同的对象。在引入带有数组的强引用之前,我在测试中遇到了一些问题-相同的地址显示相同的时钟号码,这意味着不同的对象被分配在相同的地址位置上(一旦释放了对象,另一个对象就会占据它的内存空间,即使在同一线程上运行,正如您可以从上面的示例项目中看到的那样)。 - Paulius Vindzigelskis
2个回答

1
区别在于变量的初始化代码运行的时间。对于“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被访问时才被初始化。在任何情况下,每个属性的初始化器只会运行一次。

谢谢!不知怎么的,我错过了日志的第一行,它显示了你在这里讨论的完全相同的事情 - var-as-a-closure 与父类一起初始化,而 lazy 是为了节省内存,以确保只有在需要时才分配内存。 - Paulius Vindzigelskis

1
当将其与结构/类的其他属性混合使用时,它们变得更加有趣。以下是我能想到的几个例子:

var-as-closure无法引用其他实例变量

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不保证哪个属性将首先被初始化。当fullName2被初始化时,firstName和lastName可能尚未被初始化。
你不能定义一个包含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 指向固定的内存地址,但该地址处的对象随时可以更改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接