Swift 中 Lazy var 和 var as-a-closure 之间的区别

Posted

技术标签:

【中文标题】Swift 中 Lazy var 和 var as-a-closure 之间的区别【英文标题】:Difference between Lazy var and var as-a-closure in Swift 【发布时间】:2019-03-20 08:00:48 【问题描述】:

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

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

根据这些测试,如果惰性 var 和简单 var 都被定义为闭包(最后是()),看起来它们之间没有区别。

作为闭包的变量实现会自动使变量变得惰性还是我遗漏了什么?

【问题讨论】:

时钟变量应该表示什么?描述方法被调用的次数? @brett,这是为了确保在时钟增加时调用相同的对象。在使用数组引入强引用之前,我在测试时遇到了一些问题 - 相同的地址显示相同的时钟编号,这意味着在相同的地址点上分配了不同的对象(一旦对象被释放,另一个占用它的内存空间,即使在同一个线程上运行,正如您从示例中看到的那样上面的项目)。 【参考方案1】:

不同之处在于变量的初始化代码何时运行。对于lazy vars,初始化代码在第一次访问该变量时运行。对于non-lazy vars,它在结构/类初始化时运行。

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 是与父类一起启动的,而惰性是为了节省内存以确保仅在需要时才分配它【参考方案2】:

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

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 不保证首先初始化哪个属性。 firstNamelastNamefullName2 初始化时可能尚未初始化。

您不能定义包含惰性变量的结构的常量实例

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 指向一个固定的内存地址,但该地址处的对象可以随时更改。

【讨论】:

以上是关于Swift 中 Lazy var 和 var as-a-closure 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

Swift - 以编程方式创建视图时的 Lazy Var vs. Let(节省内存)

Swift 实践:属性

swift之属性

Swift 中“static var”和“var”的区别

定义全局 android.widget var/val 时的“lateinit”或“by lazy”

Swift 中的 `let` 和 `var` 有啥区别?