尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Swift析构器:揭秘内存管理核心机制

Swift析构器:揭秘内存管理核心机制 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
返回列表