问题背景:
闭包作为概念来源于函数式语言,允许将代码块作为值传递。在Swift中,闭包(类似于其他语言中的lambda)自一开始就出现了。因此,可以优雅地实现异步调用、委托行为、排序集合、过滤等。
问题:
闭包会捕获周围上下文中的变量。如果这些变量中有指向类对象的引用,可能会出现retain cycle,特别是当闭包作为该类的属性时。还重要的是理解escaping和non-escaping闭包之间的区别,并意识到值捕获是如何工作的。
解决方案:
Swift将闭包实现为独立的对象,可以捕获上下文,而闭包的所有者在架构上需要小心。
代码示例:
class Performer { var onComplete: (() -> Void)? func doWork() { onComplete = { print("工作的完成!") } } } // 在闭包内捕获self class Test { var value = 0 func start() { DispatchQueue.global().async { [weak self] in self?.value = 1 } } }
关键特性:
如果闭包没有显式捕获self,会导致retain cycle吗?
是的,如果闭包作为类的strong property保存并在内部引用self,即使你没有显式写出self,也会导致retain cycle。使用[weak self]/[unowned self],或者在可能的情况下使闭包本地。
escaping和non-escaping闭包之间有什么区别?
escaping闭包可以在函数结束后被调用,通常用于异步操作。non-escaping在函数执行结束前执行。在escaping中,self的捕获顺序是不同的。
代码示例:
func asyncWork(completion: @escaping () -> Void) { DispatchQueue.global().async { completion() } }
闭包可以修改捕获变量的值吗?
可以,如果闭包捕获了一个声明为var的变量,它可以改变其值(对于值类型)。对于类来说,这是一个引用,属性始终可以被修改。
代码示例:
var value = 1 let closure = { value += 1 } closure() print(value) // 2
ViewController将闭包作为属性(例如,completionHandler)并直接引用self。结果形成了retain cycle:ViewController => 闭包 => ViewController。屏幕断开不会释放内存。
优点: 代码简洁且看起来很短。
缺点: 内存泄漏,ViewController在内存中“悬挂”,潜在的bug。
在闭包内使用[weak self]或[unowned self],或闭包的生命周期不超过对象的生命周期。在代码审查中审核类似的地方。
优点: 资源正确释放,无不可预测的泄漏。
缺点: [weak self]在解引用时需要小心,错误使用可能导致隐性崩溃。