Swift 析构过程

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/unownedNo(内存泄漏)
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
// 无输出!内存泄漏!

解决:使用 weakunowned

class Apartment {
    weak var tenant: Person?  // 弱引用
    // 或:unowned var tenant: Person!
    deinit { print("Apartment 释放") }
}

person = nil
apt = nil
// 输出:
// Person 释放
// Apartment 释放

weak:可选,可能为 nil
unowned:非可选,假设一定有值(更危险)


六、闭包中的强引用循环

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 对比

特性classstruct
deinitYesNo
内存管理ARC栈/堆(自动)
引用循环可能不可能
清理资源手动 deinit无需

推荐:能用 struct 就用 struct,避免 deinit 复杂性。


九、调试内存泄漏工具

  1. Xcode Instruments → Leaks
  2. Memory Graph Debugger
  3. 打印 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 个文件未关闭!
// 手动关闭所有文件...

最佳实践总结

建议说明
优先使用 structdeinit 负担
必须用 class 时,关注 deinit清理资源
避免强引用循环使用 weak / unowned
闭包中始终用 [weak self]防止 retain cycle
deinit 中打印日志调试泄漏
不要依赖 deinit 执行顺序ARC 不保证顺序

常见面试题

  1. deinit 什么时候执行?
    → 最后一个强引用移除时,ARC 自动调用
  2. 为什么 struct 没有 deinit
    → 值类型复制传递,无需手动清理。
  3. 如何打破闭包强引用循环?
    → 使用 [weak self][unowned self] 捕获列表。
  4. weakunowned 区别?
  • weak: 可选,自动设为 nil
  • unowned: 非可选,假设始终存在(崩溃风险)
  1. 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 onDisappear vs deinit

需要内存泄漏完整案例分析Instruments 实战教程,或自定义资源管理器框架?欢迎继续提问!

文章已创建 2481

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部