使用[unowned self]的风险有多大?

58 阅读2分钟

总结

在打破引用循环的时候,除非你确定你所引用的对象的生命周期比你长,你可以使用[unowned self],否则老老实实使用[weak self]进行捕获

前言

  1. 作为iOS开发,我们经常遇到循环引用,在Swift中打破引用循环的方式有两种,一种是捕获弱引用 [weak self],一种是捕获无主引用 [unowned self]
  2. 捕获无主引用有更好的性能,但是也伴随着风险

无主引用

  1. 和弱引用一样,不会持有它所引用的对象
  2. 和弱引用不同的是,无主引用总是假定一定有值,所以它修饰的变量一定不会是可选值。ARC也不会将无主引用自动置为nil
  3. 只有在你确定所引用的对象不会被dealloc的时候可以使用无主引用
  4. 如果在对象被释放后的无主引用,将会产生运行时错误,和强制解包一样,造成崩溃

看个例子

import Foundation
class ViewModel {
    private let service = Service()
    var updateUI: ((_ text: String?) -> Void)?
    func loadData() {
        service.fetchData { [unowned self] text in
            self.updateUI?(text)
        }
    }
}
  1. 在上面的例子中,ViewModel持有Service,Service的fetchData的回调函数内捕获使用了self,造成了循环引用
  2. 我们在调用service.fetchData的回调中使用[unowned self]捕获了self,从而打破循环引用
  3. 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()
    }
}
  1. Service在dataTask完成的时候调用了completionHandler
  2. 问题就在于此时没办法保证ViewModel和Service一定在内存中存在,有可能已经dealloc了,这样再调用completionHandler就会造成崩溃

个人建议

  1. 除非你写的代码位于特别底层并且对性能要求,此时在你保证所引用的对象一直存在时,可以使用unowned。比如引用了一个单例
  2. 其他99%的情况使用weak吧。而且在你不了解闭包内部实现的时候最好使用weak

资料

www.swiftwithvincent.com/newsletter/…