编程iOS 开发者

解释 Swift 中对象之间所有权自动传递(自动引用计数,ARC)的机制,以及它如何与内存管理相关?

用 Hintsage AI 助手通过面试

答案。

问题的历史:

随着 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 // 两个对象都未被释放,发生引用循环

关键特点:

  • 内存管理对开发者透明。
  • 仅支持类,结构体和枚举不通过引用追踪。
  • 解决引用循环使用 weak 和 unowned。

反常问题。

可以将 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/unowned 的地方使用强引用。
  • 在处理包含对象的类、数组/字典时忘记 ARC。
  • 尝试手动释放值类型的内存或对结构体/枚举使用 weak。

生活中的例子

负面案例

两个控制器之间存在强引用:一个通过委托属性,另一个通过子控制器数组。创建者在委托中没有使用 weak。

优点: 错误不是立刻可见,看起来一切正常。

缺点: 随着应用程序的规模增长,内存没有释放,会出现泄漏;在内存有限的情况下可能会崩溃。

正面案例

委托和事件观察者设置为 weak,控制器数组根据控制器的消失进行清理。所有内容都记录得很清楚。

优点: 没有泄漏,所有对象都能及时释放,性能没有损失。

缺点: 如果在某个地方遗忘了强引用,可能会意外丢失委托;在应用程序架构时需要谨慎。