在程序中构造了一个使用 window 来展示的弹窗类, 弹窗类内部持有一个 window, 然后通过 window 持有一个根视图控制器, 通过在根视图控制器上添加对应的显示内容, 从而显示弹窗. 但就是这样一个简单的结构, 却引起了循环引用, 下面就来进行分析.

上下文

原来的实现如下所示:

swift
typealias VoidCallBack = () -> ()
typealias DataCallBack<T> = (T) -> Void

class AlertWindowView {
    private var _window: UIWindow?
    private weak var _rootVC: UIViewController?
    
    /// 动画方式展示内容视图
    func show(content: UIView,
              onTapBG: VoidCallBack? = nil,
              initialConstratins: DataCallBack<(UIView, UIView)>?,
              normalConstraints: DataCallBack<(UIView, UIView)>?) {
        // ...
        vc.contentViewInitialConstaints = initialConstratins
        vc.contentViewNormalConstaints = normalConstraints
        // ...
    }

    func showContentWithCommonLayoutConstaints(content: UIViewController & AlertAddible,
                                               onTapBG: VoidCallBack? = nil) {
        show(content: content,
             initialConstratins: commonInitialConstraints,
             normalConstraints: commonNormalConstraints)
    }
    
    private func commonInitialConstraints(contentAndSuperView: (UIView, UIView)) { // ... }
    
    private func commonNormalConstraints(contentAndSuperView: (UIView, UIView)) { // ... }
}

private class AlertWindowViewController: UIViewController {
    // ...
    /// 初始约束
    var contentViewInitialConstaints: DataCallBack<(UIView, UIView)>?
    /// 正常显示状态下的约束
    var contentViewNormalConstaints: DataCallBack<(UIView, UIView)>?
    // ...
}

定位到在赋值 AlertWindowViewControllercontentViewInitialConstaintscontentViewNormalConstaints 两个闭包属性时, 传入的时候传入的是 AlertWindowView 的是 commonInitialConstraintscommonNormalConstraints 两个方法作为实参, 从而造成循环引用.

因为 AlertWindowView 持有 window, window 持有 AlertWindowViewController 对象, 而 AlertWindowViewController 对象中通过 contentViewInitialConstaintscontentViewNormalConstaints 持有 AlertWindowViewcommonInitialConstraints 方法, 这样会隐式地持有 AlertWindowView 对象, 从而造成循环引用.

教训

在某个对象 A 中, 持有对象 B, 而对象 B 中有闭包存储属性, 闭包从 A 中传过去的时候, 一定要注意不要直接传 A 中的方法作为闭包参数, 因为这样会造成在 B 中隐式地持有 A, 从而造成循环引用.

故上面的代码中修改如下即可:

swift
func showContentWithCommonLayoutConstaints(content: UIViewController & AlertAddible,
                                           onTapBG: VoidCallBack? = nil) {
    // 将之前如下的代码

    // show(content: content,
    //      initialConstratins: commonInitialConstraints,
    //      normalConstraints: commonNormalConstraints)
    
    // 替换为:

    show(content: content, initialConstratins: { [weak self] in
        self?.commonInitialConstraints(contentAndSuperView: $0)
        }, normalConstraints: { [weak self] in
            self?.commonNormalConstraints(contentAndSuperView: $0)
    })
}