普通视图

发现新文章,点击刷新页面。
昨天 — 2025年5月17日首页

Swift中的析构函数deinit

作者 原鸣清
2025年5月17日 07:02

析构函数

在 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 没有继承, 它们只有构造器,不存在构造链,甚至构造器都可以用默认自动生成的

❌
❌