编程iOS开发者

在Swift中解释闭包(closures)的工作原理:与函数的区别、变量捕获的特点、不当使用时可能出现的问题。

用 Hintsage AI 助手通过面试

回答

闭包(closures)在Swift中是独立的代码块,可以捕获和保存对周围上下文中的变量和常量的引用。它们允许组织回调、异步处理,并将代码的执行作为变量的值存储。

与函数的区别

  • 闭包可以捕获来自外部作用域的变量。
  • 语法更为简洁。
  • 可以作为值传递。

闭包示例:

var counter = 0 let incrementer: () -> Void = { counter += 1 } incrementer() print(counter) // 1

捕获的特点

  • 默认情况下,变量通过strong引用捕获。
  • 可以通过捕获列表(capture list)显式定义捕获规则([weak self][unowned self])。

问题

  • 主要问题是,在闭包内部捕获self时可能出现retain cycles。
  • 在多线程代码中,可能会不易察觉地捕获到过期的变量值。

诱导性问题

强链接捕获和在捕获列表中使用[weak self][unowned self]有什么区别?这有什么严重后果?

回答: 如果您不指定[weak self][unowned self],闭包将默认通过强引用捕获self,这将导致retain cycle,如果闭包和self互相引用。[weak self]通过弱引用捕获self(可选),[unowned self]通过非可选引用捕获self。错误的选择或缺少捕获列表会导致内存泄漏或崩溃。

class Test { var closure: (() -> Void)? func setup() { closure = { [weak self] in self?.doWork() } } func doWork() { ... } }

由于对主题细微差别不了解而导致的实际错误示例


故事

一位程序员在UIViewController内部的闭包中未使用捕获列表,启动了异步数据加载。结果,控制器及其视图未被释放,导致在关闭(dismiss)后出现内存泄漏。基础设施分析显示数百个"悬挂"在内存中的控制器。


故事

闭包通过[unowned self]捕获了self,但self的生命周期在闭包之前结束。访问已释放的对象时导致崩溃——应用程序在尝试处理闭包中的self时崩溃。


故事

在项目中,内嵌闭包捕获了两个变量:一个是strong,另一个是weak。结果发现,weak引用被过早置为nil,导致闭包所需的数据丢失——这个bug只在多线程环境下的压力测试中出现。