deinit
的介绍
#
在 Swift 中,deinit
是一个特殊的析构函数(或析构器),在类的实例被销毁时自动调用。deinit
的主要作用是执行清理工作,以释放资源、移除监听器或关闭任务。
Swift 中,内存管理是通过 ARC(自动引用计数,Automatic Reference Counting) 实现的。当对象的引用计数归零时,其内存会被自动释放,并调用该对象的 deinit
方法。
deinit
的函数定义
#
语法: #
deinit {
// 清理代码
}
特点: #
- 仅支持类(
class
):deinit
只能用于类,不能用于结构体(struct
)或枚举(enum
)。 - 无法手动调用:
deinit
方法会在实例销毁时自动调用,无法显式调用。 - 一个类只能有一个
deinit
:每个类只能定义一个析构方法,并且不能带参数。
deinit
的工作机制
#
- 当实例的引用计数归零时,系统会自动执行以下操作:
- 释放实例占用的内存。
- 调用该实例的
deinit
方法,执行一些清理工作。
- 如果涉及继承,子类的
deinit
会先运行,然后父类的deinit
(类似构造函数的调用顺序)。
示例:
class Parent {
deinit {
print("Parent is being deinitialized")
}
}
class Child: Parent {
deinit {
print("Child is being deinitialized")
}
}
// 创建并销毁实例
var child: Child? = Child()
child = nil
输出:
Child is being deinitialized
Parent is being deinitialized
析构顺序: #
- 子类的
deinit
先被调用。 - 父类的
deinit
后被调用。
deinit
的使用场景
#
deinit
广泛应用于资源管理、事件移除、通知清理等场景,以下是一些常见的使用情景:
1. 取消通知中心的观察者 #
在使用 NotificationCenter
注册观察者时,需要在对象销毁时移除监听器,否则会导致内存泄漏或回调错误。
class MyObserver {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification), name: NSNotification.Name("MyNotification"), object: nil)
}
deinit {
// 移除观察者,防止内存泄漏
NotificationCenter.default.removeObserver(self)
print("Observer deinitialized, notification removed")
}
@objc func handleNotification(_ notification: Notification) {
print("Notification received")
}
}
// 示例代码
var observer: MyObserver? = MyObserver()
NotificationCenter.default.post(name: NSNotification.Name("MyNotification"), object: nil) // 会触发回调
observer = nil // 移除观察者并释放内存
2. 释放资源或任务 #
当类的实例需要管理某些对系统资源的长时间占用时(如文件句柄、网络连接、数据库连接等),可以通过 deinit
在实例销毁时释放这些资源。
class FileHandler {
let filePath: String
init(filePath: String) {
self.filePath = filePath
print("File opened: \(filePath)")
}
deinit {
print("File closed: \(filePath)")
// 执行关闭文件操作
}
}
var fileHandler: FileHandler? = FileHandler(filePath: "/example/file.txt")
fileHandler = nil // 释放资源
输出:
File opened: /example/file.txt
File closed: /example/file.txt
3. 移除定时器(Timer) #
如果在类中使用了定时器(Timer
),需要在对象销毁时停止并释放定时器,避免内存泄漏或定时器继续运行导致异常。
class TimerManager {
var timer: Timer?
init() {
startTimer()
}
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
print("Timer fired")
}
}
deinit {
// 停止并释放定时器
timer?.invalidate()
print("Timer invalidated")
}
}
var manager: TimerManager? = TimerManager()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
manager = nil // 停止定时器
}
输出:
Timer fired
Timer fired
Timer invalidated
4. Socket 或网络连接的释放 #
对于某些需要长时间保持连接的网络会话(如 WebSocket),可以在 deinit
中关闭连接,确保不占用网络资源。
class WebSocketManager {
var connection: URLSessionWebSocketTask?
init(url: URL) {
let session = URLSession(configuration: .default)
connection = session.webSocketTask(with: url)
connection?.resume()
print("WebSocket connected")
}
deinit {
connection?.cancel()
print("WebSocket disconnected")
}
}
var socket: WebSocketManager? = WebSocketManager(url: URL(string: "wss://example.com/socket")!)
socket = nil // 断开连接
5. 释放自定义闭包中的强引用 #
如果在类中使用了闭包,而闭包又持有对类的强引用,可能会导致 循环引用(retain cycle),导致内存泄漏。可以在 deinit
中显式释放或处理这些闭包。
class StrongReferenceExample {
var completion: (() -> Void)?
init() {
// 创建闭包,捕获 self
completion = {
print("Captured self: \(self)")
}
}
deinit {
print("StrongReferenceExample deinitialized")
}
}
var example: StrongReferenceExample? = StrongReferenceExample()
example = nil // 销毁实例,调用 deinit
解决循环引用的方式:
- 使用
[weak self]
或[unowned self]
捕获 self,从而避免强引用。
6. 播放器或多媒体管理 #
在使用音频或视频播放器等需要占用系统多媒体资源的情况下(如 AVPlayer
),可以在 deinit
中停止播放、释放配置等。
class AudioPlayerManager {
var player: AVAudioPlayer?
init() {
// 加载音频资源并准备
}
func play() {
player?.play()
}
deinit {
player?.stop()
print("AudioPlayerManager deinitialized and stopped")
}
}
deinit
的注意事项
#
- 手动释放监听器、资源等: 某些场景(如 Timer 或通知中心)需要开发者显式移除绑定关系,避免内存泄漏。
- 继承中的
deinit
: 如果子类实现了自定义deinit
,父类的析构器会自动调用,不需要手动调用super.deinit
。 - 对象被销毁时无回调: 只有当所有强引用释放后,
deinit
才会被调用。如果对象始终有强引用指向,那么deinit
永远不会执行。 - ARC 管理内存生命周期: 确保 没有循环引用(如闭包捕获),否则引用计数无法归零,
deinit
不会触发。
总结 #
deinit
是一个非常实用的机制,用于管理类实例生命周期结束时的清理任务,适用场景包括:
- 通知中心监听器移除
- 定时器、任务取消
- 文件 / Socket / 网络连接的释放
- 播放器、资源管理
每当创建需要耗时或系统资源的对象时,始终确保在 deinit
中正确地释放资源,避免 内存泄漏 和资源占用问题。与此同时,配合 ARC 的自动内存管理可以简单且安全地完成复杂对象的生命周期管理。