Swift 内存管理与循环引用深度解析:从原理到实战 - 实践
2026-01-21 11:50 tlnshuju 阅读(1) 评论(0) 收藏 举报前言
内存问题的严重性
┌─────────────────────────────────────────────────────────────────────┐
│ 内存问题对 App 的影响 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户体验影响 │
│ ────────────── │
│ • App 变得越来越慢 │
│ • 界面卡顿、动画掉帧 │
│ • 突然闪退(被系统杀死) │
│ • 手机发热、电量消耗快 │
│ │
│ 内存泄漏的累积效应 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 内存使用 │ │
│ │ ▲ │ │
│ │ │ OOM Crash │ │
│ │ │ ╱ │ │
│ │ │ ╱ │ │
│ │ │ ╱ ← 内存泄漏累积 │ │
│ │ │ ╱ │ │
│ │ │ ╱‾‾‾‾‾╲ ╱ │ │
│ │ │ ╱ ╲ ╱ │ │
│ │ │╱ ╲ ╱ ← 正常内存波动 │ │
│ │ └──────────────────────────────────────────────▶ 时间 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 常见内存问题 │
│ ──────────── │
│ 1. 循环引用(最常见) │
│ 2. 闭包捕获 self │
│ 3. 定时器未释放 │
│ 4. 通知未移除 │
│ 5. 代理强引用 │
│ │
└─────────────────────────────────────────────────────────────────────┘
本文目标
// 学完本文,你将能够:
// 1. 理解 Swift 内存管理的底层原理
class UnderstandARC {
// 引用计数如何工作?
// 对象何时被释放?
}
// 2. 识别并解决循环引用
class Parent {
var child: Child? // 强引用
}
class Child {
weak var parent: Parent? // 弱引用,打破循环
}
// 3. 正确处理闭包中的 self
class ViewController {
func loadData() {
service.fetch {
[weak self] result in
guard let self = self else {
return }
self.updateUI(with: result)
}
}
}
// 4. 使用工具检测内存问题
// Instruments → Leaks / Allocations
// Xcode Memory Graph Debugger
// 5. 建立良好的内存管理习惯
内存基础
Swift 内存区域
┌─────────────────────────────────────────────────────────────────────┐
│ Swift 内存布局 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 高地址 ┌─────────────────────────────────────┐ │
│ │ 栈 (Stack) │ │
│ │ • 值类型(struct, enum) │ │
│ │ • 函数参数 │ │
│ │ • 局部变量 │ │
│ │ • 自动管理,快速分配/释放 │ │
│ │ ↓ 向下增长 │ │
│ ├─────────────────────────────────────┤ │
│ │ │ │
│ │ (空闲区域) │ │
│ │ │ │
│ ├─────────────────────────────────────┤ │
│ │ ↑ 向上增长 │ │
│ │ 堆 (Heap) │ │
│ │ • 引用类型(class) │ │
│ │ • 闭包捕获的变量 │ │
│ │ • 需要 ARC 管理 │ │
│ │ • 相对较慢的分配/释放 │ │
│ ├─────────────────────────────────────┤ │
│ │ 全局/静态区域 │ │
│ │ • 全局变量 │ │
│ │ • 静态变量 │ │
│ │ • 常量 │ │
│ ├─────────────────────────────────────┤ │
│ 低地址 │ 代码区 │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
值类型 vs 引用类型的内存行为
// MARK: - 值类型(栈上分配)
struct Point {
var x: Double
var y: Double
}
func valueTypeDemo() {
var p1 = Point(x: 10, y: 20)
var p2 = p1 // 值拷贝,p1 和 p2 完全独立
p2.x = 100
print(p1.x) // 10 - p1 不受影响
print(p2.x) // 100
// 函数结束时,p1 和 p2 自动从栈上释放
// 无需 ARC 管理
}
// MARK: - 引用类型(堆上分配)
class Person {
var name: String
init(name: String) {
self.name = name
print("\(name) 被创建")
}
deinit {
print("\(name) 被销毁")
}
}
func referenceTypeDemo() {
let person1 = Person(name: "Alice") // 堆上分配,引用计数 = 1
let person2 = person1 // 引用同一对象,引用计数 = 2
person2.name = "Bob"
print(person1.name) // "Bob" - 同一个对象
print(person2.name) // "Bob"
// 函数结束时:
// person2 离开作用域,引用计数 = 1
// person1 离开作用域,引用计数 = 0,对象被释放
}
// MARK: - 混合情况
struct Container {
var value: Int
var person: Person // 引用类型作为值类型的属性
}
func mixedDemo() {
let container1 = Container(value: 1, person: Person(name: "Charlie"))
var container2 = container1 // 值拷贝,但 person 是引用拷贝
container2.value = 2
container2.person.name = "David"
print(container1.value) // 1 - 值独立
print(container1.person.name) // "David" - 引用共享!
}
引用计数基础
// MARK: - 手动观察引用计数
import Foundation
class RefCountDemo {
var name: String
init(name: String) {
self.name = name
}
}
func observeRefCount() {
var obj: RefCountDemo? = RefCountDemo(name: "Test")
// 使用 CFGetRetainCount 观察(仅供调试)
print("引用计数: \(CFGetRetainCount(obj))") // 通常是 2(有额外的临时引用)
var obj2 = obj
print("添加引用后: \(CFGetRetainCount(obj))") // 3
obj2 = nil
print("移除引用后: \(CFGetRetainCount(obj))") // 2
obj = nil
// 对象被释放
}
// ⚠️ 注意:CFGetRetainCount 的值可能因编译器优化而不准确
// 它主要用于理解概念,不应在生产代码中使用
ARC原理
ARC 工作机制
┌─────────────────────────────────────────────────────────────────────┐
│ ARC (Automatic Reference Counting) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 源代码 编译器插入的代码 │
│ ──────── ──────────────── │
│ │
│ let obj = MyClass() → let obj = MyClass() │
│ swift_retain(obj) │
│ │
│ // 使用 obj → // 使用 obj │
│ │
│ // 作用域结束 → swift_release(obj) │
│ // if refCount == 0 → dealloc │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 对象内存结构: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ HeapObject │ │
│ ├────────────────────────────────────────────────────────────┤ │
│ │ ┌──────────────┐ │ │
│ │ │ Metadata │ ← 类型信息、方法表 │ │
│ │ ├──────────────┤ │ │
│ │ │ RefCounts │ ← 强引用计数 + 弱引用计数 │ │
│ │ │ ┌────────┐ │ (64位,包含额外标志位) │ │
│ │ │ │ strong │ │ │ │
│ │ │ │ weak │ │ │ │
│ │ │ │ flags │ │ │ │
│ │ │ └────────┘ │ │ │
│ │ ├──────────────┤ │ │
│ │ │ 实例变量 │ ← 对象的实际数据 │ │
│ │ │ ...... │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
引用计数操作详解
// MARK: - 引用计数变化示例
class TrackableObject {
let id: Int
init(id: Int) {
self.id = id
print("[\(id)] init - 对象创建")
}
deinit {
print("[\(id)] deinit - 对象销毁")
}
}
func demonstrateARC() {
print("=== 开始 ===")
// 1. 对象创建,引用计数 = 1
var obj1: TrackableObject? = TrackableObject(id: 1)
print("obj1 创建后")
// 2. 新增强引用,引用计数 = 2
var obj2 = obj1
print("obj2 = obj1 后")
// 3. 新增强引用,引用计数 = 3
var obj3 = obj1
print("obj3 = obj1 后")
// 4. 解除一个引用,引用计数 = 2
obj3 = nil
print("obj3 = nil 后")
// 5. 解除一个引用,引用计数 = 1
obj2 = nil
print("obj2 = nil 后")
// 6. 解除最后一个引用,引用计数 = 0,对象被销毁
obj1 = nil
print("obj1 = nil 后")
print("=== 结束 ===")
}
/*
输出:
=== 开始 ===
[1] init - 对象创建
obj1 创建后
obj2 = obj1 后
obj3 = obj1 后
obj3 = nil 后
obj2 = nil 后
[1] deinit - 对象销毁
obj1 = nil 后
=== 结束 ===
*/
ARC 在不同场景的行为
// MARK: - 函数参数传递
class Data {
var value: Int
init(value: Int) {
self.value = value }
deinit {
print("Data deinit") }
}
func processData(_ data: Data) {
// 进入函数时,data 的引用计数 +1
print("处理数据: \(data.value)")
// 函数结束时,data 的引用计数 -1
}
func parameterDemo() {
let data = Data(value: 42) // 引用计数 = 1
processData(data) // 函数内引用计数临时 +1
// data 仍然有效,引用计数 = 1
}
// MARK: - 集合中的引用
func collectionDemo() {
var array: [Data] = []
let data = Data(value: 1) // 引用计数 = 1
array.append(data) // 引用计数 = 2
array.removeAll() // 引用计数 = 1
// data 仍然有效
}
// MARK: - 可选类型
func optionalDemo() {
var optional: Data? = Data(value: 1) // 引用计数 = 1
if let unwrapped = optional {
// unwrapped 增加引用,引用计数 = 2
print(unwrapped.value)
// if 块结束,unwrapped 释放,引用计数 = 1
}
optional = nil // 引用计数 = 0,对象销毁
}
// MARK: - 属性引用
class Container {
var data: Data?
deinit {
print("Container deinit") }
}
func propertyDemo() {
let container = Container()
let data = Data(value: 1) // data 引用计数 = 1
container.data = data // data 引用计数 = 2
// 当 container 被销毁时,container.data 也会释放
// 这会使 data 引用计数 -1
}
循环引用
什么是循环引用
┌─────────────────────────────────────────────────────────────────────┐
│ 循环引用示意图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 正常情况(无循环引用): │
│ ┌─────────┐ │
│ │ 外部引用 │────────────▶┌──────────┐ │
│ └─────────┘ │ 对象 A │ │
│ │ refCount │ │
│ 当外部引用 │ = 1 │ │
│ 置为 nil 时 └──────────┘ │
│ 对象被释放 ✓ │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 循环引用(内存泄漏): │
│ ┌─────────┐ │
│ │ 外部引用 │──────X──────▶┌──────────┐ ┌──────────┐ │
│ └─────────┘ │ 对象 A │◀────▶│ 对象 B │ │
│ │ refCount │ │ refCount │ │
│ 即使外部引用 │ = 1 │ │ = 1 │ │
│ 置为 nil, └──────────┘ └──────────┘ │
│ A 和 B 互相引用, 强引用 ───────────▶ 强引用 │
│ 无法被释放 ✗ │
│ │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 打破循环引用: │
│ ┌─────────┐ │
│ │ 外部引用 │────────────▶┌──────────┐ ┌──────────┐ │
│ └─────────┘ │ 对象 A │─────▶│ 对象 B │ │
│ │ refCount │◀─ ─ ─│ refCount │ │
│ 当外部引用 │ = 1 │ weak │ = 1 │ │
│ 置为 nil 时, └──────────┘ └──────────┘ │
│ 两个对象都能 强引用 ────▶ ◀─ ─ ─ 弱引用 │
│ 正确释放 ✓ │
│ │
└─────────────────────────────────────────────────────────────────────┘
循环引用代码示例
// MARK: - 经典的循环引用
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) 被创建")
}
deinit {
print("\(name) 被销毁")
}
}
class Apartment {
let unit: String
var tenant: Person? // ❌ 强引用,造成循环引用
init(unit: String) {
self.unit = unit
print("公寓 \(unit) 被创建")
}
deinit {
print("公寓 \(unit) 被销毁")
}
}
func createRetainCycle() {
var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
// 建立循环引用
john?.apartment = unit4A // john → unit4A (强引用)
unit4A?.tenant = john // unit4A → john (强引用)
// 尝试释放
john = nil // john 的引用计数仍为 1(来自 unit4A.tenant)
unit4A = nil // unit4A 的引用计数仍为 1(来自 john.apartment)
// 两个对象都没有被释放!deinit 不会被调用
}
// MARK: - 正确的解决方案
class FixedApartment {
let unit: String
weak var tenant: Person? // ✅ 弱引用,打破循环
init(unit: String) {
self.unit = unit
print("公寓 \(unit) 被创建")
}
deinit {
print("公寓 \(unit) 被销毁")
}
}
func noRetainCycle() {
var john: Person? = Person(name: "John")
var unit4A: FixedApartment? = FixedApartment(unit: "4A")
john?.apartment = unit4A // john → unit4A (强引用)
unit4A?.tenant = john // unit4A → john (弱引用) ✅
john = nil
// john 的引用计数变为 0,被释放
// john.apartment 被释放,unit4A 引用计数 -1
unit4A = nil
// unit4A 的引用计数变为 0,被释放
// ✅ 所有对象都正确释放
}
更复杂的循环引用链
// MARK: - 三方循环引用
class ClassA {
var b: ClassB?
deinit {
print("A deinit") }
}
class ClassB {
var c: ClassC?
deinit {
print("B deinit") }
}
class ClassC {
var a: ClassA? // ❌ 形成 A → B → C → A 的循环
deinit {
print("C deinit") }
}
func threeWayCycle() {
var a: ClassA? = ClassA()
var b: ClassB? = ClassB()
var c: ClassC? = ClassC()
a?.b = b
b?.c = c
c?.a = a // 形成循环
a = nil
b = nil
c = nil
// 没有任何 deinit 被调用
}
// MARK: - 自引用
class Node {
var next: Node? // 链表节点
var previous: Node? // ❌ 双向链表如果都是强引用会有问题
deinit {
print("Node deinit") }
}
func linkedListCycle() {
let node1 = Node()
let node2 = Node()
node1.next = node2 // node1 → node2
node2.previous = node1 // node2 → node1 ❌ 循环
}
// 正确的双向链表
class FixedNode {
var next: FixedNode?
weak var previous: FixedNode? // ✅ 弱引用
deinit {
print("FixedNode deinit") }
}
Weak-Unowned
weak 详解
// MARK: - weak 引用
/*
weak 特点:
1. 不增加引用计数
2. 必须是可选类型 (?)
3. 必须是变量 (var)
4. 当引用的对象被释放时,自动变为 nil
5. 适用于可能为 nil 的情况
*/
class Teacher {
let name: String
var student: Student?
init(name: String) {
self.name = name
}
deinit {
print("Teacher \(name) deinit")
}
}
class Student {
let name: String
weak var teacher: Teacher? // ✅ 弱引用
init(name: String) {
self.name = name
}
deinit {
print("Student \(name) deinit")
}
}
func weakDemo() {
var teacher: Teacher? = Teacher(name: "Prof. Smith")
var student: Student? = Student(name: "Alice")
teacher?.student = student
student?.teacher = teacher
print("teacher?.student?.name: \(teacher?.student?.name ?? "nil")")
print("student?.teacher?.name: \(student?.teacher?.name ?? "nil")")
// 释放 teacher
teacher = nil
print("After teacher = nil:")
print("student?.teacher: \(String(describing: student?.teacher))") // nil
// student 仍然存在
student = nil
}
/*
输出:
teacher?.student?.name: Alice
student?.teacher?.name: Prof. Smith
Teacher Prof. Smith deinit
After teacher = nil:
student?.teacher: nil
Student Alice deinit
*/
unowned 详解
// MARK: - unowned 引用
/*
unowned 特点:
1. 不增加引用计数
2. 非可选类型(假设始终有值)
3. 可以是常量 (let) 或变量 (var)
4. 当引用的对象被释放后访问会崩溃!
5. 适用于确定不会为 nil 的情况
6. 性能略优于 weak(无需管理可选值)
*/
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("Customer \(name) deinit")
}
}
class CreditCard {
let number: String
unowned let customer: Customer // ✅ 无主引用
// 信用卡不可能没有持有人
// 信用卡的生命周期 <= 持有人的生命周期
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit {
print("CreditCard \(number) deinit")
}
}
func unownedDemo() {
var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234-5678", customer: customer!)
// 正常访问
print("Card owner: \(customer?.card?.customer.name ?? "nil")")
// 释放 customer
customer = nil
// CreditCard 也会被释放(因为只有 customer 持有它的强引用)
}
/*
输出:
Card owner: Bob
CreditCard 1234-5678 deinit
Customer Bob deinit
*/
// MARK: - unowned 的危险
func unownedDanger() {
class Parent {
var child: Child?
deinit {
print("Parent deinit") }
}
class Child {
unowned let parent: Parent
init(parent: Parent) {
self.parent = parent
}
deinit {
print("Child deinit") }
}
var child: Child?
do {
let parent = Parent()
child = Child(parent: parent)
parent.child = child
}
// parent 在这里被释放
// ❌ 危险!访问已释放的 unowned 引用会崩溃
// print(child?.parent) // Crash!
}
weak vs unowned 选择指南
// MARK: - 选择指南
/*
┌─────────────────────────────────────────────────────────────────────┐
│ weak vs unowned 选择指南 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 使用 weak 当: │
│ ───────────── │
│ • 引用的对象可能在任何时候变为 nil │
│ • 两个对象的生命周期相互独立 │
│ • 不确定哪个对象会先被释放 │
│ • 需要检查引用是否仍然有效 │
│ │
│ 使用 unowned 当: │
│ ───────────────── │
│ • 确定引用的对象在自己被释放前不会变为 nil │
│ • 两个对象有明确的从属关系 │
│ • 性能要求较高(避免可选值开销) │
│ • 被引用对象的生命周期 >= 当前对象的生命周期 │
│ │
├─────────────────────────────────────────────────────────────────────┤
│
浙公网安备 33010602011771号