编程iOS 开发者

Swift 中闭包的值捕获和引用捕获机制是如何工作的?在捕获时可能会出现哪些潜在危险,如何避免这些危险?

用 Hintsage AI 助手通过面试

答案。

在 Swift 中,闭包可以 "捕获" 周围上下文中的值和引用。如果捕获的值是值类型(结构体、枚举),闭包会复制数据。如果捕获的是引用(例如类),闭包会保存对类实例的引用,这可能会导致引用循环(retain cycle),如果不使用 [weak self] 或 [unowned self]。

捕获机制允许实现异步任务和回调,但不当使用会影响内存管理。

示例:

class MyClass { var value = 10 func doSomething() { let closure = { [weak self] in print(self?.value ?? "nil") } closure() } }

在这个示例中使用了 [weak self],以避免强引用循环,因为在进行异步操作。

有陷阱的问题。

问题: “使用 [weak self] 是否总能保证在闭包中捕获 self 时不会发生内存泄漏?”

回答: 不一定,如果在闭包内部出现了强引用(例如,通过临时变量),即使使用了 [weak self] 也可能会导致引用循环。例如:

let closure = { [weak self] in let strongSelf = self strongSelf?.doSomething() }

如果将闭包绑定到一个长生命周期的对象(例如 NotificationCenter),则在 self 有其他引用时可能会导致引用循环。

由于对主题细节的不了解而产生的实际错误示例。


故事 1

在一个 iOS 应用项目中,开发者忘记在传递到网络请求的闭包中添加 [weak self]。结果在请求执行期间,即使用户关闭了屏幕,视图控制器对象也没有被释放。这导致在频繁的网络操作中发生了内存泄漏。


故事 2

在一个复杂的定时器订阅系统中,通过未使用 weak 的闭包订阅的对象,即使在屏幕被从内存中移除后仍继续执行其操作。这导致对已经不在层级中的 UI 元素的重复调用,从而导致应用程序崩溃。


故事 3

在实现数据缓存时,闭包在长生命周期缓存对象中捕获了对 self 的引用,而没有使用 [unowned self] 注释。在原始对象被销毁后,从闭包中访问 self 会导致访问已经释放的内存并崩溃应用程序。