做iOS开发的朋友应该都清楚,我们日常写业务代码,闭包真的是随处可见。网络请求回调、动画回调、GCD异步操作,还有页面反向传值,基本上全都要用到闭包。
不过我发现很多开发都有个刻板习惯:写闭包就无脑加 weak self,只知道不加会内存泄漏,但根本不懂背后的原理。平时都是复制粘贴写代码,遇到特殊场景就不知道该怎么处理了。比如这几个常见问题:
闭包的循环引用到底是怎么形成的?
同样是解决泄漏,到底该用weak还是unowned?
有些闭包明明没加weak self,却完全不会内存泄漏,这是为什么?
今天我就结合自己平时开发的踩坑经验,用最简单直白的话讲清楚这些知识点,看完就能直接用到项目中,不用死记硬背。
一、闭包到底为啥会出现循环引用?
Swift闭包有个最容易踩坑的特性,也是内存泄漏的根本原因:闭包会自动抓取代码里用到的外部变量,而且针对控制器、自定义类这种引用类型,默认都是强引用。
我用项目里最常见的控制器案例,简单演示一下,所有人都能看懂:
class TestVC: UIViewController { var block: (() -> ())? override func viewDidLoad() { super.viewDidLoad() block = { // 闭包内部使用了 self self.doSomething() } } func doSomething() {} }
这里的引用逻辑特别简单,就是互相持有、解绑不开:
当前控制器强持有了我们定义的 block 属性
闭包代码里用到了 self,就会反过来强引用这个控制器
一来一回就形成了双向强引用,两个对象互相绑死,谁都释放不了谁。这就导致我们退出当前页面后,控制器根本不会被系统销毁,一直占着内存,也就是内存泄漏。时间久了,APP内存占用越来越高,就会出现卡顿、闪退等问题。
二、日常最优解:weak self
平时开发中,绝大多数场景用 weak self 就完全够用了。它的原理很简单,就是把闭包对 self 的强引用改成弱引用,打破双向绑定的关系,页面退出后控制器就能正常释放,从根源解决内存泄漏问题。