使用闭包时总是需要使用[weak self]吗?

当一个对象或值在逃逸闭包中被引用时,它将被捕获,以便在执行该闭包时可用。默认情况下,当一个对象被捕获时,它将使用强引用来保留——引用self的情况下,在某些情况下可能导致循环引用。

例如,下面的代码在闭包中捕获self,闭包本身最终也被同一对象引用,导致一个循环引用:

class ProductViewController: UIViewController {
    private lazy var buyButton = Button()
    private let purchaseController: PurchaseController
    
    ...

    override func viewDidLoad() {
        super.viewDidLoad()

        // Since our buyButton retains its closure, and our
        // view controller in turn retains that button, we'll
        // end up with a retain cycle by capturing self here:
        buyButton.handler = {
            self.showShoppingCart()
            self.purchaseController.startPurchasingProcess()
        }
    }

    private func showShoppingCart() {
        ...
    }
}

例如,为了解决上述问题,我们可以使用[weak self]捕获列表 - 这将打破我们的循环引用,现在捕捉self使用一个弱引用:

buyButton.handler = { [weak self] in
    guard let self = self else { return }
    self.showShoppingCart()
    self.purchaseController.startPurchasingProcess()
}

然而,这并不意味着捕获self总是导致一个循环引用——实际上只有在上述类型的情况下,这样的问题才会发生。例如,以下代码不会导致保留周期:

class HomeViewController: UIViewController {
    ...

    override func viewDidLoad() {
        super.viewDidLoad()

        if shouldShowPromotion {
            // Capturing self within this closure does not cause
            // a retain cycle, since our view controller does not
            // retain this dispatch queue in any way:
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                self.showPromotionView()
            }
        }
    }

    private func showPromotionView() {
        ...
    }
}

有一件重要的事情需要考虑,当我们像上面那样捕捉self时,只要闭包本身在内存中(在这种情况下,是两秒钟),我们就会保留它。因此,虽然从技术上讲,我们可能不会在这些情况下导致一个循环引用,但我们最终可能会延长某些对象的生命周期,这可能会导致意外的行为。

综上所述:

  1. 使用[weak self]只在以下情况下才需要,即强引用self最终会导致一个循环引用,例如,当self在一个闭包中被捕获时,该闭包最终也会被同一对象保留。
  2. 使用[weak self]在处理将存储较长时间的闭包时也是一个好主意,因为在这样的闭包中强捕获一个对象将导致它在内存中保留相同的时间。
  3. 在所有其他情况下,使用[weak self]是可选的,但添加它通常没有坏处——除非我们出于某种原因想强捕获self