
1. 引言在 Swift 中析構過程Deinitialization是類實例在被釋放前執行清理工作的機制。與 Objective-C 的dealloc方法類似Swift 通過deinit關鍵字定義析構器用于釋放資源、保存狀態或執行必要的收尾操作。本文將深入探討 Swift 析構過程的工作原理并通過豐富的代碼實例幫助讀者全面掌握這一重要特性。2. 析構過程的基本原理Swift 使用自動引用計數ARC來管理類實例的內存。當一個類的實例不再被強引用時ARC 會自動釋放該實例的內存并在釋放前調用其析構器deinit。析構器具有以下特點每個類最多只能有一個析構器。析構器不接受任何參數也不返回任何值。析構器由系統在實例釋放前自動調用不能手動調用。析構器使用deinit關鍵字聲明不需要寫func關鍵字。3. 基礎析構器示例下面是一個最簡單的析構器示例演示了deinit的基本語法和調用時機class BankAccount { var balance: Double init(balance: Double) { self.balance balance print(賬戶創建成功初始余額\(balance) 元) } deinit { print(賬戶已銷毀釋放相關資源) } } // 使用示例 var account: BankAccount? BankAccount(balance: 1000.0) account nil // 輸出 // 賬戶創建成功初始余額1000.0 元 // 賬戶已銷毀釋放相關資源當account被設置為nil時該實例不再被強引用ARC 立即釋放其內存并自動調用deinit方法。4. 析構器中的資源清理析構器最常見的用途是釋放外部資源例如關閉文件句柄、斷開網絡連接或釋放數據庫連接。下面通過一個文件管理器的示例來說明import Foundation class FileManagerHandler { let filePath: String var fileHandle: FileHandle? init(filePath: String) { self.filePath filePath // 模擬打開文件 fileHandle FileHandle(forReadingAtPath: filePath) print(文件已打開\(filePath)) } func readData() - Data? { return fileHandle?.readDataToEndOfFile() } deinit { // 關閉文件句柄釋放資源 fileHandle?.closeFile() print(文件已關閉資源已釋放\(filePath)) } } // 使用示例 var handler: FileManagerHandler? FileManagerHandler(filePath: /tmp/example.txt) let data handler?.readData() handler nil // 輸出 // 文件已打開/tmp/example.txt // 文件已關閉資源已釋放/tmp/example.txt5. 析構器與類繼承在 Swift 中子類會繼承父類的析構器。當子類實例被釋放時會先執行子類的析構器再執行父類的析構器。這與初始化過程先執行父類初始化再執行子類初始化的順序正好相反。class Animal { var name: String init(name: String) { self.name name print(\(name) 初始化完成) } deinit { print(\(name) 的父類析構器執行) } } class Dog: Animal { var breed: String init(name: String, breed: String) { self.breed breed super.init(name: name) print(\(name) 子類初始化完成) } deinit { print(\(name) 的子類析構器執行) } } // 使用示例 var dog: Dog? Dog(name: 旺財, breed: 金毛) dog nil // 輸出 // 旺財 初始化完成 // 旺財 子類初始化完成 // 旺財 的子類析構器執行 // 旺財 的父類析構器執行6. 析構器與可選類型只有類類型的可選變量才能觸發析構器。當可選變量被設置為nil時如果該變量是該實例的最后一個強引用ARC 就會釋放實例并調用析構器。下面通過一個日志記錄器的示例來說明class Logger { let identifier: String init(identifier: String) { self.identifier identifier print(日志記錄器 \(identifier) 已創建) } func log(_ message: String) { print([\(identifier)] \(message)) } deinit { print(日志記錄器 \(identifier) 已銷毀) } } // 使用示例 var logger: Logger? Logger(identifier: main) logger?.log(應用啟動) logger nil logger?.log(這條日志不會輸出) // 輸出 // 日志記錄器 main 已創建 // [main] 應用啟動 // 日志記錄器 main 已銷毀7. 析構器與閉包捕獲當閉包捕獲了類實例的self時如果閉包被該實例持有就會形成循環引用導致析構器永遠不會被調用。下面通過一個反面示例來說明這個問題class NetworkManager { var onComplete: (() - Void)? init() { print(網絡管理器已創建) } func startRequest() { // 閉包捕獲 self形成循環引用 onComplete { print(請求完成回調執行) self.handleResponse() } } func handleResponse() { print(處理響應數據) } deinit { print(網絡管理器已銷毀) } } // 使用示例 var manager: NetworkManager? NetworkManager() manager?.startRequest() manager nil // 輸出 // 網絡管理器已創建 // 注意析構器沒有被調用因為存在循環引用為了解決循環引用問題可以使用[weak self]或[unowned self]捕獲列表class NetworkManagerFixed { var onComplete: (() - Void)? init() { print(網絡管理器已創建) } func startRequest() { // 使用 weak self 打破循環引用 onComplete { [weak self] in guard let self self else { return } print(請求完成回調執行) self.handleResponse() } } func handleResponse() { print(處理響應數據) } deinit { print(網絡管理器已銷毀) } } // 使用示例 var fixedManager: NetworkManagerFixed? NetworkManagerFixed() fixedManager?.startRequest() fixedManager nil // 輸出 // 網絡管理器已創建 // 網絡管理器已銷毀8. 析構器與存儲屬性在析構器中可以訪問實例的所有存儲屬性包括可選屬性和常量屬性。但需要注意的是析構器執行完畢后實例的內存才會被真正釋放因此不能在析構器中訪問已經被釋放的屬性。class DatabaseConnection { let connectionID: String var isConnected: Bool var lastQuery: String? init(connectionID: String) { self.connectionID connectionID self.isConnected true print(數據庫連接 \(connectionID) 已建立) } func executeQuery(_ query: String) { lastQuery query print(執行查詢\(query)) } deinit { // 在析構器中訪問存儲屬性 print(正在關閉數據庫連接\(connectionID)) print(最后執行的查詢\(lastQuery ?? 無)) isConnected false print(連接狀態已更新\(isConnected)) } } // 使用示例 var connection: DatabaseConnection? DatabaseConnection(connectionID: conn-001) connection?.executeQuery(SELECT * FROM users) connection nil // 輸出 // 數據庫連接 conn-001 已建立 // 執行查詢SELECT * FROM users // 正在關閉數據庫連接conn-001 // 最后執行的查詢SELECT * FROM users // 連接狀態已更新false9. 析構器與單例模式單例模式中實例通常被全局持有因此析構器在應用生命周期內不會被調用。下面通過一個配置管理器的示例來說明class AppConfiguration { static let shared AppConfiguration() var theme: String light var language: String zh-CN private init() { print(配置管理器初始化) } func printConfiguration() { print(主題\(theme)語言\(language)) } deinit { // 單例的析構器通常不會被調用 print(配置管理器析構正常情況下不會執行) } } // 使用示例 let config AppConfiguration.shared config.printConfiguration() // 輸出 // 配置管理器初始化 // 主題light語言zh-CN10. 析構器與結構體需要特別注意的是結構體struct和枚舉enum是值類型不支持析構器。只有類class這種引用類型才支持deinit。下面通過一個對比示例來說明// 結構體不支持析構器 struct Point { var x: Double var y: Double // 編譯錯誤結構體不能包含析構器 // deinit { // print(Point 析構) // } } // 類支持析構器 class Circle { var center: Point var radius: Double init(center: Point, radius: Double) { self.center center self.radius radius print(圓形創建成功) } func area() - Double { return Double.pi * radius * radius } deinit { print(圓形已銷毀) } } // 使用示例 var circle: Circle? Circle(center: Point(x: 0, y: 0), radius: 5.0) print(面積\(circle?.area() ?? 0)) circle nil // 輸出 // 圓形創建成功 // 面積78.53981633974483 // 圓形已銷毀11. 析構器與自動釋放池在 Swift 中ARC 會在最后一次強引用被移除時立即釋放實例不需要像 Objective-C 那樣依賴自動釋放池。但在某些場景下理解釋放時機仍然很重要。下面通過一個批量處理示例來說明class Task { let taskID: Int init(taskID: Int) { self.taskID taskID print(任務 \(taskID) 創建) } func execute() { print(任務 \(taskID) 執行中) } deinit { print(任務 \(taskID) 銷毀) } } // 使用示例 func processTasks() { for i in 1...3 { let task Task(taskID: i) task.execute() // 每次循環結束后task 離開作用域立即釋放 } } processTasks() // 輸出 // 任務 1 創建 // 任務 1 執行中 // 任務 1 銷毀 // 任務 2 創建 // 任務 2 執行中 // 任務 2 銷毀 // 任務 3 創建 // 任務 3 執行中 // 任務 3 銷毀12. 析構器與通知中心在 iOS 開發中使用通知中心NotificationCenter時需要在析構器中移除觀察者避免內存泄漏和野指針問題。下面通過一個示例來說明import Foundation class NotificationObserver { let observerName: String init(observerName: String) { self.observerName observerName // 添加通知觀察者 NotificationCenter.default.addObserver( self, selector: #selector(handleNotification(_:)), name: NSNotification.Name(CustomNotification), object: nil ) print(\(observerName) 已注冊通知觀察者) } objc func handleNotification(_ notification: Notification) { print(\(observerName) 收到通知\(notification.userInfo ?? [:])) } deinit { // 移除通知觀察者避免野指針 NotificationCenter.default.removeObserver(self) print(\(observerName) 已移除通知觀察者) } } // 使用示例 var observer: NotificationObserver? NotificationObserver(observerName: 主觀察者) NotificationCenter.default.post( name: NSNotification.Name(CustomNotification), object: nil, userInfo: [message: Hello] ) observer nil // 輸出 // 主觀察者 已注冊通知觀察者 // 主觀察者 收到通知[message: Hello] // 主觀察者 已移除通知觀察者13. 析構器與定時器使用定時器Timer時如果定時器持有對目標的強引用也會導致循環引用。正確的做法是在析構器中使定時器失效。下面通過一個示例來說明import Foundation class TimerManager { var timer: Tim