Swift中的析构函数deinit
析构函数
在 Swift 中,deinit(析构函数)只适用于 class 类型。因为只有类是引用类型,而不像结构体 struct 或枚举 enum 是值类型。
虽然 Swift 自动管理内存(ARC,Automatic Reference Counting),你不需要经常写 deinit,但在某些关键场景中非常有用。以下是最常见且实用的使用场景:
常用场景一:移除通知监听者
使用 NotificationCenter 添加观察者时,如果不手动移除,会导致内存泄漏或重复响应事件。
class MyObserver {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(handleEvent), name: .someEvent, object: nil)
}
@objc func handleEvent() {
print("Event received")
}
deinit {
NotificationCenter.default.removeObserver(self)
print("Observer deinitialized")
}
}
常用场景二:取消定时器、任务、订阅等异步资源
比如 Timer、DispatchSourceTimer、Combine 的 AnyCancellable、URLSession task 等等。
class TimerHandler {
var timer: Timer?
init() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
print("Tick")
}
}
deinit {
timer?.invalidate()
print("Timer invalidated")
}
}
常用场景三:断开委托(Delegate)或弱引用检查
有时候你需要在 deinit 中把自己从其他类的 delegate 中移除,以避免无用引用。
class Child: SomeDelegate {
init(parent: Parent) {
parent.delegate = self
}
deinit {
print("Child released")
// 这里通常不强制清空 delegate,但有时你想显式处理
}
}
常用场景四:清理自定义资源或临时文件
当类负责写入临时文件或打开文件句柄等,需要在析构时清理。
class TempFileManager {
let filePath: String
init() {
filePath = NSTemporaryDirectory() + UUID().uuidString
FileManager.default.createFile(atPath: filePath, contents: nil)
}
deinit {
try? FileManager.default.removeItem(atPath: filePath)
print("Temporary file deleted")
}
}
常用场景五:调试或资源释放追踪
打印 deinit 是常见的调试手段,能够帮助判断是否某对象已经被释放(尤其是在处理循环引用、Retain Cycle 的时候)。
deinit {
print("MyViewController has been released!")
}
总结:deinit 适用的典型场景
场景 | 用途说明 |
---|---|
通知监听移除 | 防止内存泄漏 |
定时器/任务取消 | 释放异步资源 |
文件或自定义资源清理 | 避免残留资源 |
delegate/数据源解绑(可选) | 规范化资源管理 |
Combine/任务订阅取消 | 清理绑定或订阅 |
打印调试(检查是否释放) | 非常实用的调试手段 |
在 SwiftUI 或 iOS 架构设计中,有些类如 ViewModel 或自定义对象,还是推荐善用 deinit 来做资源释放和问题定位。
构造链
Swift 中 enum、struct 和 class 都可以拥有构造函数(initializer),但三者在构造行为和使用方式上有 明显差异,尤其是在以下几个地方:
• 默认构造器是否自动生成
• 成员变量初始化规则
• 继承与重写支持
• 可变性
• 引用与值语义
这里主要讨论下构造链。构造在 Swift 中,class 是引用类型,支持继承。而子类初始化时需要 保证父类的属性也被正确初始化,因此构造函数需要遵循一定的调用顺序,也就是所谓的“构造链”:
• 子类的 init 必须在某一阶段调用 super.init(...),以确保父类部分正确构造。
• 所有 stored property 初始化完后,才能调用 super.init()。
• Swift 编译器会严格检查构造顺序,避免未初始化状态下访问属性。
class Animal {
var name: String
init(name: String) {
self.name = name
print("Animal init")
}
}
class Dog: Animal {
var breed: String
init(name: String, breed: String) {
self.breed = breed // 1️⃣ 先初始化子类属性
super.init(name: name) // 2️⃣ 调用父类构造器
print("Dog init")
}
}
// 调用
let dog = Dog(name: "Buddy", breed: "Labrador")
// 输出
# Animal init
# Dog init
对于 class类型:
1. 所有存储属性必须初始化。
2. 子类构造器必须在合适时机调用 super.init(...)。
3. 若父类没有默认构造器,子类必须手动调用父类指定构造器。
4. convenience init 只能调用本类的 designated init,不能直接调用 super.init。
5. 父类的 required init 必须被子类实现。
"struct 和 enum 没有继承, 它们只有构造器,不存在构造链,甚至构造器都可以用默认自动生成的