总结
在打破引用循环的时候,除非你确定你所引用的对象的生命周期比你长,你可以使用[unowned self]
,否则老老实实使用[weak self]
进行捕获
前言
- 作为iOS开发,我们经常遇到循环引用,在Swift中打破引用循环的方式有两种,一种是捕获弱引用
[weak self]
,一种是捕获无主引用[unowned self]
- 捕获无主引用有更好的性能,但是也伴随着风险
无主引用
- 和弱引用一样,不会持有它所引用的对象
- 和弱引用不同的是,无主引用总是假定一定有值,所以它修饰的变量一定不会是可选值。ARC也不会将无主引用自动置为nil
- 只有在你确定所引用的对象不会被dealloc的时候可以使用无主引用
- 如果在对象被释放后的无主引用,将会产生运行时错误,和强制解包一样,造成崩溃
看个例子
import Foundation
class ViewModel {
private let service = Service()
var updateUI: ((_ text: String?) -> Void)?
func loadData() {
service.fetchData { [unowned self] text in
self.updateUI?(text)
}
}
}
- 在上面的例子中,ViewModel持有Service,Service的fetchData的回调函数内捕获使用了self,造成了循环引用
- 我们在调用service.fetchData的回调中使用
[unowned self]
捕获了self,从而打破循环引用 - Service作为ViewModel的一个属性,它会和ViewModel一起dealloc,这样看起来使用
[unowned self]
好像没太大问题,但其实并没有保证Service的生命周期一定比ViewModel长,所以还是有问题的
我们看下Service中fetchData的代码,就能看出问题在哪了
import Foundation
class Service {
func fetchData(_ completionHandler: @escaping (String?) -> Void) {
let url = URL(string: "https://my-api.com/my-endpoint")!
let request = URLRequest(url: url)
URLSession
.shared
.dataTask(with: request) { data, response, error in
let decoded = String(data: data!, encoding: .utf8)
completionHandler(decoded)
}
.resume()
}
}
- Service在dataTask完成的时候调用了completionHandler
- 问题就在于此时没办法保证ViewModel和Service一定在内存中存在,有可能已经dealloc了,这样再调用completionHandler就会造成崩溃
个人建议
- 除非你写的代码位于特别底层并且对性能要求,此时在你保证所引用的对象一直存在时,可以使用
unowned
。比如引用了一个单例 - 其他99%的情况使用
weak
吧。而且在你不了解闭包内部实现的时候最好使用weak