Swift 中的析构过程(Deinitialization)是对象生命周期的最后阶段,仅适用于类(class)类型(结构体和枚举没有析构器)。当一个类的实例被释放时,deinit 方法会自动被调用,用于清理资源、关闭连接、释放内存等。
一、基本语法
deinit {
// 清理代码
print("对象被释放")
}
只能在
class中使用
不能手动调用
没有参数、没有返回值
不能被重写(override)
二、核心示例
class FileManager {
let filename: String
init(filename: String) {
self.filename = filename
print("打开文件: \(filename)")
}
func write(_ content: String) {
print("写入: \(content)")
}
deinit {
print("关闭并释放文件: \(filename)")
}
}
// 使用
var file: FileManager? = FileManager(filename: "data.txt")
file?.write("Hello, Swift!")
file = nil // 引用计数为 0 → 触发 deinit
// 输出:
// 打开文件: data.txt
// 写入: Hello, Swift!
// 关闭并释放文件: data.txt
三、ARC 与析构时机
Swift 使用 自动引用计数(ARC) 管理内存:
| 情况 | 触发 deinit |
|---|---|
| 最后一个强引用被移除 | Yes |
存在强引用循环(无 weak/unowned) | No(内存泄漏) |
weak 引用 | 不阻止释放 |
四、典型使用场景
| 场景 | 清理操作 |
|---|---|
| 文件操作 | 关闭文件句柄 |
| 网络连接 | 断开 socket |
| 数据库 | 关闭连接 |
| 定时器 | invalidate() |
| 通知观察者 | removeObserver |
| 缓存清理 | 释放大内存 |
示例:网络连接管理
class NetworkClient {
private var connection: URLSession?
init() {
connection = URLSession(configuration: .default)
print("网络连接已建立")
}
func fetchData() {
// 使用 connection...
}
deinit {
connection?.invalidateAndCancel()
print("网络连接已断开")
}
}
五、强引用循环 → 内存泄漏
class Person {
var apartment: Apartment?
deinit { print("Person 释放") }
}
class Apartment {
var tenant: Person? // 强引用!
deinit { print("Apartment 释放") }
}
var person: Person? = Person()
var apt: Apartment? = Apartment()
person?.apartment = apt
apt?.tenant = person
person = nil
apt = nil
// 无输出!内存泄漏!
解决:使用 weak 或 unowned
class Apartment {
weak var tenant: Person? // 弱引用
// 或:unowned var tenant: Person!
deinit { print("Apartment 释放") }
}
person = nil
apt = nil
// 输出:
// Person 释放
// Apartment 释放
weak:可选,可能为nilunowned:非可选,假设一定有值(更危险)
六、闭包中的强引用循环
class Counter {
var value = 0
lazy var increment: () -> Void = {
self.value += 1 // 闭包捕获 self → 强引用
}
deinit { print("Counter 释放") }
}
var counter: Counter? = Counter()
counter?.increment()
counter = nil
// 无输出!内存泄漏!
解决:捕获列表 [weak self] 或 [unowned self]
lazy var increment: () -> Void = { [weak self] in
guard let self = self else { return }
self.value += 1
}
// 或
lazy var increment: () -> Void = { [unowned self] in
self.value += 1
}
七、deinit 不能做的事
| 禁止操作 | 原因 |
|---|---|
override deinit | 编译错误 |
手动调用 deinit | 编译错误 |
| 返回值 | 无意义 |
| 抛出错误 | 不支持 throws |
八、与 struct 对比
| 特性 | class | struct |
|---|---|---|
deinit | Yes | No |
| 内存管理 | ARC | 栈/堆(自动) |
| 引用循环 | 可能 | 不可能 |
| 清理资源 | 手动 deinit | 无需 |
推荐:能用
struct就用struct,避免deinit复杂性。
九、调试内存泄漏工具
- Xcode Instruments → Leaks
- Memory Graph Debugger
- 打印
deinit日志
deinit {
print("释放: \(self)")
}
十、完整实战示例:资源管理器
class ResourceManager {
private var files: [String] = []
func openFile(named name: String) {
files.append(name)
print("打开: \(name)")
}
func closeAll() {
print("手动关闭所有文件...")
files.removeAll()
}
deinit {
if !files.isEmpty {
print("警告:仍有 \(files.count) 个文件未关闭!")
}
closeAll()
}
}
// 使用
do {
let manager = ResourceManager()
manager.openFile(named: "config.json")
manager.openFile(named: "user.db")
// 退出作用域 → 自动 deinit
}
// 输出:
// 打开: config.json
// 打开: user.db
// 警告:仍有 2 个文件未关闭!
// 手动关闭所有文件...
最佳实践总结
| 建议 | 说明 |
|---|---|
优先使用 struct | 无 deinit 负担 |
必须用 class 时,关注 deinit | 清理资源 |
| 避免强引用循环 | 使用 weak / unowned |
闭包中始终用 [weak self] | 防止 retain cycle |
deinit 中打印日志 | 调试泄漏 |
不要依赖 deinit 执行顺序 | ARC 不保证顺序 |
常见面试题
deinit什么时候执行?
→ 最后一个强引用移除时,ARC 自动调用。- 为什么
struct没有deinit?
→ 值类型复制传递,无需手动清理。 - 如何打破闭包强引用循环?
→ 使用[weak self]或[unowned self]捕获列表。 weak和unowned区别?
weak: 可选,自动设为nilunowned: 非可选,假设始终存在(崩溃风险)
deinit可以被重写吗?
→ 不能,编译错误。
小技巧
// 1. 安全解包 + 弱引用
lazy var action: () -> Void = { [weak self] in
guard let self = self else { return }
self.performTask()
}
// 2. 临时强引用(避免中途释放)
func process(with handler: @escaping () -> Void) {
let strongSelf = self
Task {
await heavyOperation()
strongSelf.updateUI()
}
}
// 3. 使用 @MainActor 确保 UI 线程
@MainActor
deinit {
print("UI 相关资源已释放")
}
高级话题(可继续提问)
- ARC 优化与
dealloc底层 - Swift 与 Objective-C
dealloc混编 - 引用循环检测工具原理
- Actor 中的
deinit - SwiftUI
onDisappearvsdeinit
需要内存泄漏完整案例分析、Instruments 实战教程,或自定义资源管理器框架?欢迎继续提问!