在 HarmonyOS Next 开发中,终结器(Finalizer)是管理对象生命周期的重要机制,用于在对象被垃圾回收时执行资源释放等清理操作。本文基于《仓颉编程语言开发指南》,解析终结器的特性、使用场景及与资源管理的最佳实践。

一、终结器的基础定义与语法

终结器通过 ~init 关键字声明,无参数、无返回值,在对象内存回收前自动调用。

1. 基本用法示例

class FileHandle {
    private var fd: CInt // 模拟文件描述符

    init(path: String) {
        fd = open(path, O_RDONLY) // 打开文件(简化示例)
        println("文件打开:\(path)")
    }

    ~init() {
        if fd != -1 {
            close(fd) // 关闭文件描述符
            println("文件关闭:\(fd)")
        }
    }
}

// 使用场景:作用域结束后自动释放资源
func processFile() {
    let handle = FileHandle(path: "/data.txt") // 初始化时打开文件
    // 使用handle处理文件...
    // 函数执行完毕后,handle被回收,终结器自动调用
}

2. 终结器的限制条件

  • 不可显式调用:终结器由垃圾回收器自动触发,禁止手动调用 handle.~init()
  • open:包含终结器的类不可使用 open 修饰,防止子类重写导致资源泄漏;
  • 线程安全:终结器可能在任意线程执行,避免访问线程敏感资源。

二、终结器与资源管理场景

1. 非托管资源释放

终结器最常见的用途是释放非托管资源(如C语言内存、文件句柄、网络连接):

class NativeMemory {
    private var ptr: UnsafeMutablePointer<Void>?

    init(size: Int) {
        ptr = malloc(size) // 分配原生内存
    }

    ~init() {
        ptr?.deallocate() // 释放内存
        ptr = nil
    }
}

2. 事件监听器清理

在注册全局事件监听器时,终结器可确保对象销毁时自动解绑:

class EventListener {
    init() {
        EventBus.register(self) // 注册事件监听
    }

    ~init() {
        EventBus.unregister(self) // 反注册事件监听
    }

    func onEvent(event: Event) { /* 处理事件 */ }
}

3. 日志与调试信息记录

终结器可用于记录对象生命周期信息,辅助调试:

class DebugObject {
    private let id: String = UUID().toString()

    init() {
        println("对象创建:\(id)")
    }

    ~init() {
        println("对象销毁:\(id)")
    }
}

三、终结器的执行机制与陷阱

1. 执行时机的不确定性

  • 终结器触发时机依赖垃圾回收器调度,可能在对象不可达后的任意时刻执行;
  • 避免依赖终结器执行实时性要求高的操作(如网络请求)。

示例:不可靠的实时逻辑

class RealTimeTimer {
    ~init() {
        sendHeartbeat() // 可能因延迟执行导致逻辑失败
    }
}

2. 循环引用与终结器失效

循环引用会导致对象无法被回收,终结器可能永远不会执行:

class A {
    var ref: B?
    ~init() { println("A销毁") }
}

class B {
    var ref: A?
    ~init() { println("B销毁") }
}

func createCycle() {
    let a = A()
    let b = B()
    a.ref = b // 循环引用:A→B→A
    b.ref = a
    // a和b均无法被回收,终结器不执行
}

解决方案:使用弱引用(weak)打破循环:

class A {
    weak var ref: B? // 弱引用,不阻止回收
}

3. 继承与终结器调用顺序

子类终结器会在父类终结器之后执行:

open class Parent {
    ~init() { println("父类销毁") }
}

class Child <: Parent {
    ~init() { println("子类销毁") } // 先调用子类终结器,再调用父类
}

let child = Child() // 输出:子类销毁 → 父类销毁

四、替代方案与最佳实践

1. 优先使用RAII模式

通过use语句或自定义作用域管理资源,比终结器更可控:

func processResource() {
    let resource = Resource() // 初始化
    defer { resource.release() } // 在作用域结束时释放资源
    // 使用resource...
} // 自动调用release(),无需依赖终结器

2. 终结器与try-finally结合

在可能抛出异常的场景中,确保资源释放:

class DatabaseConnection {
    func query() throws {
        do {
            // 执行查询
            throw Error("查询失败")
        } finally {
            close() // 无论是否抛出异常,均执行清理
        }
    }

    ~init() { close() } // 作为额外保障
}

3. 避免复杂逻辑

终结器应仅执行轻量级清理,避免:

  • 调用可能抛出异常的函数;
  • 访问其他可能已被回收的对象;
  • 执行耗时操作(如磁盘写入)。

五、总结:终结器的适用边界

HarmonyOS Next 的终结器在资源管理中扮演“安全网”角色,其设计原则如下:

  • 最后防线:作为RAII模式的补充,处理无法通过作用域管理的资源;
  • 轻量级操作:仅执行必要的清理逻辑,避免影响系统性能;
  • 局限性认知:不依赖终结器实现关键业务逻辑,优先使用显式释放机制。

SameX
1 声望2 粉丝