iOS开发,键值编码(KVC)、键值观察(KVO)详细介绍

315 阅读2分钟

键路径(keyPath)、键值编码(KVC)和键值观察(KVO)详解


1. 键路径(KeyPath)

定义
键路径是用字符串表示的属性访问路径,支持嵌套属性(如 address.city)。

作用

  • 通过字符串动态访问属性,解耦代码与具体属性名的依赖。
  • 用于 KVC 和 KVO 中,指定需要操作或观察的属性路径。

原理

  • 运行时解析字符串路径,逐级访问对象的属性。
  • Swift 中通过 #keyPath 在编译时检查路径有效性,避免拼写错误。

代码示例

// Objective-C
NSString *keyPath = @"address.city"; // 直接使用字符串
// Swift
class Person: NSObject {
    @objc var address: Address
}

class Address: NSObject {
    @objc var city: String
}

let keyPath = #keyPath(Person.address.city) // 编译时安全检查

2. 键值编码(KVC - Key-Value Coding)

定义
通过字符串间接访问或修改对象属性的机制,无需直接调用存取方法。

作用

  • 动态读写属性,支持私有属性(需权限)。
  • 简化数据操作,常用于数据绑定、序列化等场景。

原理

  • 基于 NSObject 的 valueForKey: 和 setValue:forKey: 方法。
  • 按 get<Key><key>is<Key> 顺序查找方法,若未找到则直接访问实例变量。

代码示例

// Objective-C
Person *person = [Person new];
[person setValue:@"New York" forKeyPath:@"address.city"];
NSString *city = [person valueForKeyPath:@"address.city"];
// Swift
class Person: NSObject {
    @objc var name: String = ""
}

let person = Person()
person.setValue("John", forKey: "name") // 写入
let name = person.value(forKey: "name") as? String // 读取

3. 键值观察(KVO - Key-Value Observing)

定义
一种观察者模式,监听对象属性的变化并通知观察者。

作用

  • 实现数据与 UI 的自动同步(如 Model-View 绑定)。
  • 响应属性变化的通用解决方案。

原理

  • 动态生成被观察类的子类,重写 setter 方法。
  • 修改对象的 isa 指针指向新子类,拦截属性赋值操作。
  • 在 setter 中调用 willChangeValueForKey: 和 didChangeValueForKey:,触发通知。

代码示例

// Objective-C
// 添加观察者
[person addObserver:self 
        forKeyPath:@"name" 
           options:NSKeyValueObservingOptionNew 
           context:nil];

// 实现回调
- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        NSLog(@"Name changed: %@", change[NSKeyValueChangeNewKey]);
    }
}

// 移除观察者
[person removeObserver:self forKeyPath:@"name"];
// Swift
class Observer: NSObject {
    func observe(person: Person) {
        person.observe(.name, options: [.new]) { (_, change) in
            print("Name changed: (change.newValue ?? "")")
        }
    }
}

// 被观察类需标记 @objc dynamic
class Person: NSObject {
    @objc dynamic var name: String = ""
}

总结对比

特性KVCKVOKeyPath
核心作用动态读写属性监听属性变化提供属性路径的字符串表示
依赖NSObject 和 @objc 属性NSObject@objc dynamic属性无特殊要求
Swift 兼容需继承 NSObject 和 @objc需 @objc dynamic 修饰属性推荐使用 #keyPath
典型场景数据绑定、序列化Model-View 同步、状态监听配合 KVC/KVO 使用

注意事项

  • KVO 在 Swift 中需严格遵循 @objc dynamic 标记。
  • 手动触发 KVO 需调用 willChange 和 didChange 方法。
  • 使用 #keyPath 可避免硬编码字符串的错误。