文章目录

Swift 内存管理:ARC 与 weak 引用

发布于 2026-04-07 02:10:21 · 浏览 13 次 · 评论 0 条

Swift 内存管理:ARC 与 weak 引用

在 Swift 中编写应用时,系统会自动处理绝大部分内存分配与回收工作,这一机制被称为 ARC(自动引用计数)。理解 ARC 与 weak(弱引用)的配合逻辑,能彻底避免应用运行时的“内存泄漏”问题,让软件运行更流畅、更稳定。


阶段一:理解 ARC 的核心计数逻辑

ARC 的核心任务是跟踪每个类实例被引用的次数。你可以将其视为一个计数器。每当有一个变量指向某个对象,计数器加一;每当变量被置空或离开作用域,计数器减一。计数器归零时,系统立即释放该对象占用的内存。

区分三种引用类型,是避免内存错误的第一步。

对照 下表明确引用类型的具体行为:

引用类型 关键字声明 是否增加引用计数 允许为 nil 典型使用场景
强引用 默认(无需关键字) 绝大多数对象持有关系
弱引用 weak var 委托代理 delegate、闭包捕获
无主引用 unowned 子对象生命周期与父对象完全同步

阶段二:定位并识别强引用循环

当两个或多个对象互相持有对方的强引用时,它们的引用计数器永远不会归零,导致内存无法释放。这就是强引用循环(Retain Cycle),也是 iOS 开发中最常见的内存泄漏原因。

创建 测试文件以复现该问题:

  1. 新建 Swift 文件 MemoryTest.swift
  2. 输入 以下存在循环引用的代码结构:
    
    class Person {
     let name: String
     // 默认是强引用
     var apartment: Apartment?
     init(name: String) { self.name = name }
     deinit { print("\(name) 被释放") }
    }

class Apartment {
let unit: String
// 默认是强引用,形成双向绑定
var tenant: Person?
init(unit: String) { self.unit = unit }
deinit { print("公寓 (unit) 被释放") }
}

3. **绑定** 两个实例:
```swift
var xiaoMing: Person? = Person(name: "小明")
var unitA: Apartment? = Apartment(unit: "A102")
xiaoMing?.apartment = unitA
unitA?.tenant = xiaoMing
  1. 断开 外部强引用:
    xiaoMing = nil
    unitA = nil
  2. 观察 控制台输出。此时控制台不会打印任何 deinit 信息,证明两个对象的内存已被永久锁定。

阶段三:使用 weak 打破循环

解决强引用循环的标准做法是:在双向关系中,将一端改为弱引用。通常将“从属方”或“代理方”设为弱引用。

修改 Apartment 类的定义:

  1. 找到 tenant 属性声明行。
  2. 添加 weak 关键字并修改为可选类型(弱引用必须声明为 var?,因为对象被释放时引用会自动变为 nil):
    class Apartment {
     let unit: String
     weak var tenant: Person? // 核心修改点
     init(unit: String) { self.unit = unit }
     deinit { print("公寓 \(unit) 被释放") }
    }
  3. 重新执行 阶段二的绑定与断开逻辑。
  4. 确认 控制台依次打印 小明 被释放公寓 A102 被释放。内存已成功回收。

编写 安全的闭包捕获:
闭包默认强引用其内部使用的外部变量,极易引发循环。请 使用 [weak self] 捕获列表进行隔离:

  1. 声明 包含闭包属性的类:
    class DataFetcher {
     var url: String
     // 闭包可能强引用当前实例
     var onFinish: (() -> Void)?
     init(url: String) { self.url = url }
     deinit { print("下载器被释放") }
    }
  2. 设置 闭包并 添加 [weak self] 修饰符:
    var fetcher: DataFetcher? = DataFetcher(url: "api/data")
    fetcher?.onFinish = { [weak self] in
     // self 在此处是弱引用类型 (DataFetcher?)
     guard let self = self else { return }
     print("下载完成,地址:\(self.url)")
    }
  3. 释放 实例:
    fetcher = nil
  4. 验证 闭包是否阻止了实例释放。控制台将正常打印 下载器被释放,证明捕获列表生效。

阶段四:在 Xcode 中可视化检测泄漏

仅靠 deinit 打印无法覆盖所有测试场景。使用 Xcode 内置工具可以直观定位循环。

启用 内存图调试器:

  1. 运行 应用并触发可能存在泄漏的业务流程。
  2. 点击 调试区域底部的 Debug Memory Graph 按钮(图标形如多个方块堆叠)。
  3. 暂停 运行并等待 Xcode 生成依赖关系图。
  4. 查看 左侧导航栏。系统会用紫色菱形图标标记存在循环引用的对象。
  5. 点击 异常对象。中间画布将显示完整的引用链,箭头颜色区分引用类型:蓝色实线代表强引用,橙色虚线代表弱引用。
  6. 定位 代码位置。右侧检查器会直接列出 FileLine,点击可直接跳转至产生强引用的代码行。

配置 运行时检测器(辅助发现):

  1. 打开 项目编辑器(Command + Shift + E)。
  2. 选择 Run 配置页签。
  3. 切换Diagnostics 选项卡。
  4. 勾选 Malloc Stack 下的 Log All MallocsEnable Zombie Objects
  5. 开启 此项后,若代码尝试访问已释放的弱引用所指向的悬空内存,Xcode 会直接抛出异常日志,快速暴露逻辑漏洞。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文