Swift 语言从版本 6.3 起,允许 weak let 了,之前必须是 weak var。
class C {}
weak var ref: C?
这需要我们稍微调整一下 let 常量的心理模型,之前我们认为,let 常量的值是不变的,不允许运行时把 ref 改为nil,因此必须是 weak var。《The Swift Programming Language》的原文是
我们来分析一下这条语句
weak var ref: C?
这条语句定义了弱引用 ref,ref 是可选类型,是特殊的枚举类型 Optinal,本质上是Optinal 的关联值是 C 实例的引用,weak 表明这个引用是弱引用,不会使 C 实例的引用计数增加,并且在 C 实例释放时,ARC 自动使 ref 由 case some(C) 变为 case none,即 nil。ref 是值类型,它的值确实变了。let 常量的值不变,因此 ref 必须为变量。
现在允许
weak let ref: C?
ref 是常量,仍然是弱引用,在 C 实例释放时,仍然会自动变为 nil。ref 的值确实变了,这与我们 let 常量的值保持不变的心理模型不符。现在我们需要稍微调整一下,let 常量我们只赋一次值,不能再次赋值,允许运行时自动改为 nil,let 常量从来就不是“值永远不变”,而是“用户代码不能通过赋值语句修改”。
即使在 Swift 6.3 之前,let 常量的含义也存在矛盾的地方。看下面的代码,
struct WeakRef {
weak var ref: C?
}
var c: C? = C()
let s = WeakRef(ref: c)
c = nil
print(s.ref as Any) // 打印为 nil
结构 WeakRef 有存储属性 ref,是 C 实例的弱引用。变量 c 是 C 实例的强引用,s 是 WeakRef 类型常量,ref 与 c 引用同一个 C 实例,在 c 设置为 nil 后,这个 C 实例会被释放,ARC 会自动把 ref 改为 nil,ref 的值变了,整个 s 的值也变了,而 s 是常量,这就是矛盾的地方。
根据我们对 let 常量的新的理解,weak var 和这个地方就不矛盾了,并且 weak let 也合理了。s 是值类型 let 常量,不能写 s = ... 或 s.ref = ...。
有weak var 存储属性的 class 不能遵守 Sendable 协议。
final class C: Sendable {}
final class VarUser: Sendable {
weak var ref1: C? // error: Stored property 'ref1' of 'Sendable'-conforming class 'VarUser' is mutable
}
提案 SE-0481 允许 weak let,就解决了这个问题。
final class LetUser: Sendable {
weak let ref1: C?
}
以上代码在 Xcode 26.6 (Swift version 6.3.3) 验证。