问题的历史:
随着 Objective-C 的发展和 Swift 的出现,苹果提出了自动且安全地管理应用程序内存的问题,而不需要程序员显式使用 retain/release 命令。Swift 使用自动引用计数(ARC),允许跟踪对类对象的引用数量,并在最后一个所有者消失时自动释放内存。
问题:
由于 Swift 是一种多范式语言,它操作值类型(结构体 struct,枚举 enum)和引用类型(类 class)。对于后者,确保内存得到及时清理至关重要,否则可能会发生内存泄漏。问题会变得复杂,当发生引用循环(retain cycle)时,即两个对象相互引用,导致 ARC 无法释放内存。
解决方案:
Swift 仅对类对象应用 ARC。当引用数量变为 0 时,内存会被释放。为了解决引用循环问题,使用 weak/unowned 关键字。
代码示例:
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name) 正在被析构") } } class Pet { var owner: Person? init() {} deinit { print("宠物正在被析构") } } var tom: Person? = Person(name: "汤姆") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // 两个对象都未被释放,发生引用循环
关键特点:
可以将 ARC 应用于值类型吗?
不可以,ARC 仅跟踪类对象的引用。结构体和枚举按值传递,Swift 会自动释放它们的内存(通常在超出作用域时)。
如果在两个相互引用的对象的属性中不使用 weak/unowned,会发生什么?
会发生循环引用(retain cycle),导致 ARC 永远无法释放这些对象的内存。这样的代码会导致内存泄漏——永远使用 weak(或 unowned)至少在类似关系的一方。
如果有 weak,unowned 有什么用?
unowned 用于引用在另一个对象的生命周期内始终存在并且绝不会变为 nil 的案例。weak 是可为 nil 的引用,unowned 是不可为 nil 的引用,但不会增加引用计数。
代码示例:
class A { var b: B? } class B { unowned var a: A // 在 B 的生命周期内永远不会为 nil }
两个控制器之间存在强引用:一个通过委托属性,另一个通过子控制器数组。创建者在委托中没有使用 weak。
优点: 错误不是立刻可见,看起来一切正常。
缺点: 随着应用程序的规模增长,内存没有释放,会出现泄漏;在内存有限的情况下可能会崩溃。
委托和事件观察者设置为 weak,控制器数组根据控制器的消失进行清理。所有内容都记录得很清楚。
优点: 没有泄漏,所有对象都能及时释放,性能没有损失。
缺点: 如果在某个地方遗忘了强引用,可能会意外丢失委托;在应用程序架构时需要谨慎。