在程序中构造了一个使用 window 来展示的弹窗类, 弹窗类内部持有一个 window, 然后通过 window 持有一个根视图控制器, 通过在根视图控制器上添加对应的显示内容, 从而显示弹窗. 但就是这样一个简单的结构, 却引起了循环引用, 下面就来进行分析.
上下文
原来的实现如下所示:
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)>? // ... }
定位到在赋值 AlertWindowViewController 的 contentViewInitialConstaints 和 contentViewNormalConstaints 两个闭包属性时, 传入的时候传入的是 AlertWindowView 的是 commonInitialConstraints 和 commonNormalConstraints 两个方法作为实参, 从而造成循环引用.
因为 AlertWindowView 持有 window, window 持有 AlertWindowViewController 对象, 而 AlertWindowViewController 对象中通过 contentViewInitialConstaints 或 contentViewNormalConstaints 持有 AlertWindowView 的 commonInitialConstraints 方法, 这样会隐式地持有 AlertWindowView 对象, 从而造成循环引用.
教训
在某个对象 A 中, 持有对象 B, 而对象 B 中有闭包存储属性, 闭包从 A 中传过去的时候, 一定要注意不要直接传 A 中的方法作为闭包参数, 因为这样会造成在 B 中隐式地持有 A, 从而造成循环引用.
故上面的代码中修改如下即可:
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) }) }