Deinit

deinit 的介绍 #

在 Swift 中,deinit 是一个特殊的析构函数(或析构器),在类的实例被销毁时自动调用deinit 的主要作用是执行清理工作,以释放资源、移除监听器或关闭任务。

Swift 中,内存管理是通过 ARC(自动引用计数,Automatic Reference Counting) 实现的。当对象的引用计数归零时,其内存会被自动释放,并调用该对象的 deinit 方法。


deinit 的函数定义 #

语法: #

deinit {
    // 清理代码
}

特点: #

  1. 仅支持类(classdeinit 只能用于类,不能用于结构体(struct)或枚举(enum)。
  2. 无法手动调用deinit 方法会在实例销毁时自动调用,无法显式调用。
  3. 一个类只能有一个 deinit:每个类只能定义一个析构方法,并且不能带参数。

deinit 的工作机制 #

  • 当实例的引用计数归零时,系统会自动执行以下操作:
    1. 释放实例占用的内存。
    2. 调用该实例的 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 的注意事项 #

  1. 手动释放监听器、资源等: 某些场景(如 Timer 或通知中心)需要开发者显式移除绑定关系,避免内存泄漏。
  2. 继承中的 deinit 如果子类实现了自定义 deinit,父类的析构器会自动调用,不需要手动调用 super.deinit
  3. 对象被销毁时无回调: 只有当所有强引用释放后,deinit 才会被调用。如果对象始终有强引用指向,那么 deinit 永远不会执行。
  4. ARC 管理内存生命周期: 确保 没有循环引用(如闭包捕获),否则引用计数无法归零,deinit 不会触发。

总结 #

deinit 是一个非常实用的机制,用于管理类实例生命周期结束时的清理任务,适用场景包括:

  1. 通知中心监听器移除
  2. 定时器、任务取消
  3. 文件 / Socket / 网络连接的释放
  4. 播放器、资源管理

每当创建需要耗时或系统资源的对象时,始终确保在 deinit 中正确地释放资源,避免 内存泄漏 和资源占用问题。与此同时,配合 ARC 的自动内存管理可以简单且安全地完成复杂对象的生命周期管理。

本文共 1672 字,上次修改于 Jan 11, 2025