在 iOS 开发中,强引用(Strong Reference)是指对象之间默认的引用关系,它会持有对象并阻止其被释放。若形成强引用循环(Retain Cycle),会导致内存泄漏,即使对象不再使用也无法被 ARC(自动引用计数)回收。要有效防止强引用循环,需从设计、编码和工具链三个层面综合施策。

第一,使用 弱引用(Weak Reference) 打破循环。在 Swift 中,用 weak 关键字声明可选类型弱引用,用 unowned 声明非可选弱引用(需确保引用对象生命周期不小于自身)。在 Objective-C 中,使用 __weak 或 weak 属性(如 delegate、dataSource 通常应设为 weak)。最常见的场景是:Block/闭包被对象持有,而闭包内部又强引用了该对象,此时应在闭包捕获列表中声明 [weak self] 或 [unowned self],例如:networkRequest { [weak self] in ... }。
第二,遵循 delegate 模式 时,务必使用弱引用。例如 UITableViewDelegate、UIScrollViewDelegate 等协议属性,在声明时使用 weak 或 assign(Objective-C 中,assign 需配合 unsafe_unretained 注意野指针风险)。切勿用 strong 持有 delegate,否则视图控制器与委托对象可能互相持有形成循环。
第三,注意 NSTimer / CADisplayLink 的强引用陷阱。定时器会对 target 产生强引用,如果 target 又持有定时器,则形成循环。应使用 block-based 的定时器 API(如 Timer.scheduledTimer(withTimeInterval:repeats:block:)),并在 block 中弱引用 self;或在 viewWillDisappear / deinit 中手动 invalidate() 定时器并置为 nil。iOS 10+ 也可使用基于 block 的 Timer 配合 [weak self]。
第四,使用 捕获列表(Capture List) 处理闭包中的强引用。除了 [weak self],若捕获某个实例变量或局部对象,也需考虑是否可能形成引用环。对于不会长期存活的闭包(如 UIView.animate),强引用通常无碍;但 存储型闭包(属性值、回调属性)必须警惕。在 Swift 中,还可使用 [weak delegate]、[unowned owner] 等方式。
第五,利用 值类型(Struct/Enum) 减少引用需求。Swift 中,struct 和 enum 为值类型,不会被引用计数管理,因此不存在强引用循环。在模型层、配置层等场景优先使用值类型,可从根源避免问题。但需注意值类型嵌套引用类型时,仍可能间接产生循环。
第六,借助 工具与编译器 检测和预防。Xcode 的 Instruments 中的 Leaks 和 Allocations 模板能检测循环引用。静态分析工具(如 Analyze、SwiftLint)可标记疑似强引用循环。在 Swift 中,deinit 方法未被调用是发现循环的常见手段。在 Objective-C 中,MLeaksFinder 等第三方库也能辅助定位。
第七,注意 父子对象关系 与 单例 的特殊处理。对于树形结构(如父视图持有子视图,子视图以 weak 引用父视图),通常父强、子弱。单例对象持有闭包时,闭包内捕获其他对象也可能造成永久内存占用。常见做法是:外层对象强引用单例,单例内通过 weak 或 unowned 引用外部对象,或使用 通知(NotificationCenter) 代替直接回调(通知中心本身是单例,需注意 block observer 的移除)。
第八,在 Core Foundation / C 语言接口 交互中,需严格遵循内存管理规则。例如 CFBridgingRetain / CFBridgingRelease 使用不当可能导致过度保留(over-retain)或非预期强引用。应确保 __bridge 转换不引入额外所有权,并对 C 指针手动释放。
综上,防止强引用循环 的核心思维是:明确所有权归属,避免双向持有,在必要处斜向弱化。具体包括:Delegate 使用 weak、Block 中弱化 self、定时器及时失效、合理使用捕获列表、用值类型替代部分类。同时,结合 ARC 的特性和工具持续检测,才能构建健壮且无内存泄漏的 iOS 应用。

查看详情

查看详情