
没错,这段代码的意思就是“老子就是老子”!让我们一起来看看这段莫名其妙的代码究竟有什么奥秘。首先,它是要解决什么问题呢?我们来看下面这一段视频:

从上面的关系中我们基本可以总结出,当多个对象之间相互持有强引用的话,那么谁都无法从中被释放,这是非常糟糕的情况,我们通常称为修罗场引用循环。那么在 Swift 里的引用循环是什么样的呢?我们可以看看 Memory Debugger(Xcode 自带)所创作的艺术作品:

https://github.com/LiulietLee/Pick-Color/issues/2
推友 @oskargroth 还遇到过更夸张的:

https://twitter.com/oskargroth/status/1106693954991476745
Swift 中的引用方式有三种:强引用、弱引用(weak)和无主引用(unowned)。具体的可以看 The Swift Programming Language 里关于 Automatic Reference Counting(自动引用计数)的介绍,不过简单总结一下的话就是
强引用:默认,抓着其他对象不放,直到自己的生命结束
弱引用:佛系引用,对象在就有值,对象没了就是 nil,所以必须是可选类型。
无主引用:强制解包的弱引用,对象在一切安好,对象没了直接崩溃
所以当两个对象互相持有对方的强引用的时候,比如下面文件夹和文件的例子:

三角关系最稳定(笑)
大家都等着对方先放手,直到天荒地老(程序耗尽内存停止运行),谁也逃不掉。
最简单的方法:能不写 self 的时候就不写,当 Xcode 提示必须加上 self,比如下面这个错误示范里面这样,那就是可能会造成引用循环:

改编自我的 macOS 应用自动深色模式(Dynamic Dark Mode)源代码
但是,上面那个文件系统的例子导致的引用循环就不能通过这种方式发现,所以我们需要用到 Memory Debugger。运行你的程序之后,点击这个按钮就能看到对象之间的引用情况:

https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/debugging_with_xcode/chapters/special_debugging_workflows.html
有的时候,甚至你看 memory graph 也没办法搞明白究竟是什么导致了内存泄漏。那又怎么办呢?建议看看 CS193p,研究研究如何使用 Instruments 这个复杂但是功能强大的软件:


最偷懒的解决方式就是,每次 Xcode 让你加 self 的时候就听它的,但你还另外在闭包的最开始加上这行全是关键字的代码:
它会先弱引用 self,然后确认我们能够暂时强引用 self,在闭包运行结束之后就释放对 self 的引用,这样就能解决大部分闭包导致的引用循环了!不过,如果出现多层闭包嵌套(甚至是 callback hell )的情况呢?

很常见的代码,后台下载数据,主线程更新 UI
这样写是正确的吗?并不,因为 async 的闭包已经把 self 转为了强引用,所以 animate 的闭包用的 self 也是强引用的,有可能造成引用循环。所以应该改写成这样:

注意多加的一行 guard let ...
你以为这样就完了?naïve!因为 SR-3805 里提到的编译器的行为(或者叫做一般人不知道的坑),为了能够让内层的闭包弱引用 self,外层的闭包默认强引用了 self,也就是说 dataTask 里的 self 是强引用。所以正确的写法是:

这段代码我都差点写错了
注意 [weak self] 是在最外层的闭包声明的,但 guard let 是在第二层闭包才有,大家可以想想为什么。最后想说的是,上面都只是为了示范,其实如果可以的话,就一直用 weak self 也是没有问题的:

避免了 guard let 也避免了很多麻烦,不过并不是什么时候都能这么用的
稍微有些复杂,因为我们期望的是当文件被移除的时候文件夹不会死拽着不放,所以我们需要数组能够存储弱引用。因此,我们需要引入一个新的类型来包装一下:

注意,当文件消失之后,包装它的 weak 容器还会存在,但 value 会变成 nil
但更常见的问题其实是不正确的代理模式导致引用循环。比如这样会导致根目录(root)和文件系统(fileSystem)循环引用:

如果觉得这个代理模式的例子不够常见的话,可以去看看 View 到 Controller 之间的通信
而正确的代理模式应该弱引用 delegate:

注意新增的 AnyObject 要求,因为只有类的实例能够被引用,值类型是不行的
就是这样,喵~
这次的篇幅偏长,大家如果有不懂的地方可以在评论区里提问。如果觉得对你有所帮助的话不妨分享给朋友,点个赞,投个币,收个藏什么的~
啥?520 没有对象?自己还不会 init 一个么!