面试题KVO: Swift 用KVO 实现 属性监听

110 阅读2分钟

在 Swift 中,KVO(Key-Value Observing)是一种观察属性变化的机制,通常用于监听 Objective-C 对象的属性变化。由于 Swift 中的原生类并不直接支持 KVO,因此需要使用 @objc 和 dynamic 修饰符来启用 KVO 功能。

以下是使用 KVO 实现属性监听的步骤和示例:


1. 实现 KVO 的基本步骤

  1. 标记属性为 @objc 和 dynamic

    • @objc:使属性对 Objective-C 运行时可见。
    • dynamic:告诉编译器使用动态派发,而不是静态派发,以便支持 KVO。
  2. 添加观察者

    • 使用 addObserver(_:forKeyPath:options:context:) 方法添加观察者。
  3. 实现观察者回调

    • 实现 observeValue(forKeyPath:of:change:context:) 方法,处理属性变化。
  4. 移除观察者

    • 在不需要监听时,使用 removeObserver(_:forKeyPath:) 移除观察者,避免内存泄漏。

2. 示例代码

以下是一个简单的示例,演示如何使用 KVO 监听一个属性的变化:

import Foundation

// 1. 定义一个类,并标记需要监听的属性为 @objc 和 dynamic @objc class User: NSObject { @objc dynamic var name: String

init(name: String) {
    self.name = name
}

}

// 2. 定义观察者 class Observer: NSObject { var user: User

init(user: User) {
    self.user = user
    super.init()

    // 3. 添加观察者
    user.addObserver(self, forKeyPath: "name", options: [.new, .old], context: nil)
}

deinit {
    // 4. 移除观察者
    user.removeObserver(self, forKeyPath: "name")
}

// 5. 实现观察者回调
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "name" {
        let oldName = change?[.oldKey] as? String ?? "未知"
        let newName = change?[.newKey] as? String ?? "未知"
        print("属性 name 发生变化: 旧值 = \(oldName), 新值 = \(newName)")
    }
}

}

// 测试 let user = User(name: "Alice") let observer = Observer(user: user)

// 修改属性,触发 KVO user.name = "Bob" user.name = "Charlie"

3. 代码说明

  1. @objc dynamic var name: String

    • 将 name 属性标记为 @objc 和 dynamic,使其支持 KVO。
  2. addObserver

    • 在 Observer 类的初始化方法中,为 user 对象的 name 属性添加观察者。
  3. observeValue(forKeyPath:of:change:context:)

    • 当 name 属性发生变化时,会触发此方法,打印旧值和新值。
  4. removeObserver

    • 在 Observer 类的 deinit 方法中移除观察者,避免内存泄漏。

4. 注意事项

  • KVO 的局限性

    • KVO 是基于 Objective-C 运行时的特性,因此只能用于继承自 NSObject 的类。
    • 在 Swift 中,KVO 的使用不如 SwiftUI 中的 @State 和 @Binding 方便,建议在纯 Swift 项目中优先使用 SwiftUI 或 Combine 框架。
  • 线程安全

    • KVO 的观察者回调可能在任意线程触发,因此需要注意线程安全问题。
  • 性能开销

    • KVO 会引入一定的性能开销,尤其是在监听大量属性时。