代码改变世界

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                         │
│   • 两个对象有明确的从属关系                                         │
│   • 性能要求较高(避免可选值开销)                                   │
│   • 被引用对象的生命周期 >= 当前对象的生命周期                       │
│                                                                      │
├─────────────────────────────────────────────────────────────────────┤
│