Swift 基础语法
Memory Management

内存管理

一、ARC(Automatic Reference Counting)机制

什么是 ARC?

ARC 是编译器层面的内存管理机制,自动在合适的位置插入 retain、release 和 autorelease 调用,从而减少开发者手动管理内存的负担。

它的核心思想是引用计数(Reference Counting):每个对象维护一个引用计数,当引用计数为 0 时,对象被释放。

ARC 的基本原理

  • 当创建一个对象时(例如 let obj = MyClass()),系统为它分配内存并将引用计数设为 1。
  • 每当有新的强引用(strong)指向该对象时,引用计数 +1。
  • 当某个强引用被释放或置为 nil 时,引用计数 -1。
  • 当引用计数降为 0 时,系统调用 deinit 销毁对象。

二、引用类型与内存泄漏

Strong(强引用)

默认引用类型。对象被强引用时不会被释放。

示例:

class Person {
    var pet: Dog?
}
class Dog {
    var owner: Person?
}

var p: Person? = Person()
var d: Dog? = Dog()
p?.pet = d
d?.owner = p // 强引用循环!

问题: Person 和 Dog 相互强引用,导致两者的引用计数永远不会为 0,发生内存泄漏(retain cycle)

Weak(弱引用)

不会增加引用计数,引用对象释放后会自动变为 nil。

适用于可选类型(必须为 var,且为 Optional)。

class Dog {
    weak var owner: Person?
}

常见使用场景:

  • Delegate(代理)模式(例如 delegate 通常为 weak)
  • 闭包中避免循环引用([weak self])

Unowned(无主引用)

不会增加引用计数,对象释放后不会自动置为 nil,若访问已释放对象会导致崩溃。

class CreditCard {
    unowned let owner: Customer
    init(owner: Customer) {
        self.owner = owner
    }
}

使用场景:

  • 两个对象生命周期绑定,其中一个一定晚于另一个释放。
  • 例如父对象持有子对象的强引用,而子对象通过 unowned 引用父对象。

三、循环引用(Retain Cycle)

1. 对象间循环引用

class A {
    var b: B?
}
class B {
    var a: A?
}

解决:让其中一个引用为 weak 或 unowned。

2. 闭包循环引用

闭包会捕获 self,导致对象与闭包相互引用。

解决方法:

class ViewController {
    var handler: (() -> Void)?
    func setup() {
        handler = { [weak self] in
            self?.doSomething()
        }
    }
}

四、AutoreleasePool

概念

AutoreleasePool 用于延迟释放对象。

在 Swift 中较少直接使用,但在与 Objective-C 交互或创建大量临时对象时仍有意义。

示例:

autoreleasepool {
    for i in 0..<1000 {
        let obj = NSString(format: "Item %d", i)
        // 临时对象在此作用域结束后被释放
    }
}

系统层面

RunLoop 每次循环时都会自动创建并销毁一个 AutoreleasePool,确保局部临时对象及时释放。

五、内存管理在多线程下的注意点

  • 引用计数操作是原子性的,但对象的使用不是线程安全的。
  • 不同线程同时访问同一对象可能导致崩溃,需要使用同步机制(如 DispatchQueue、NSLock)。
  • AutoreleasePool 作用域与线程绑定:每个线程需自行管理其池。

六、内存泄漏的检测手段

  • Instruments → Leaks / Allocations:可视化查看内存曲线与泄漏对象。
  • Xcode Memory Graph Debugger:直接在运行时查看对象引用关系。
  • weak self 检查:常见于闭包、异步回调。
  • 工具库:如 FBRetainCycleDetector 自动检测循环引用。

七、面试常见问题总结

问题答案要点
ARC 是编译器还是运行时特性?编译器特性,通过插入 retain/release 代码实现。
weak 和 unowned 的区别?weak 自动置 nil、安全但可选;unowned 不置 nil、风险更高但效率略高。
闭包捕获 self 导致循环引用如何解决?使用 [weak self] 或 [unowned self]。
AutoreleasePool 的作用?控制临时对象生命周期,防止瞬时内存峰值。
Swift 的结构体和类在内存管理上有何区别?结构体是值类型,存放在栈上;类是引用类型,存放在堆上并受 ARC 管理。

要真正掌握内存管理,关键是理解生命周期与所有权的关系,ARC 只是帮你执行“清理”,但逻辑上仍要靠开发者设计清晰的引用结构。