在 iOS 中,KVO(Key-Value Observing)是一个强大的观察机制,它的底层实现相对复杂。KVO 利用 Objective-C 的动态特性,为对象的属性提供观察能力。
KVO 的底层实现
1. 动态子类化
当一个对象的属性被添加观察者时,KVO 会在运行时动态地创建该对象的子类,并重写该属性的 setter 方法。
- 动态创建子类 :KVO 会创建一个新的类,这个新类是被观察对象的子类,通常这个类的名字是
_NSKVOClassName_ClassName
形式。 - 重写 setter 方法:在这个动态创建的子类中,KVO 会重写被观察属性的 setter 方法。
2. 重写 setter 方法
重写后的 setter 方法在属性值发生变化时,会进行以下操作:
- 触发
willChangeValue(forKey:)
:通知即将发生变化。 - 调用原始 setter 方法:通过消息转发机制调用原始的 setter 方法,以实际更新属性值。
- 触发
didChangeValue(forKey:)
:通知变化已经发生,触发观察者回调。
3. 动态方法解析
在 KVO 动态创建的子类中,使用 method_setImplementation
方法来重写属性的 setter 方法。
objc
void setAge(id self, SEL _cmd, int newAge) {
[self willChangeValueForKey:@"age"];
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
((void (*)(struct objc_super *, SEL, int))objc_msgSendSuper)(&superStruct, _cmd, newAge);
[self didChangeValueForKey:@"age"];
}
KVO 的实现细节
以下是一个简单的示例,展示了 KVO 的一些底层实现细节:
objc
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end
@implementation Person
@end
Person *person = [[Person alloc] init];
NSLog(@"Original class: %@", object_getClass(person)); // 输出原始类
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"Class after adding observer: %@", object_getClass(person)); // 输出动态子类
[person setAge:30];
[person removeObserver:self forKeyPath:@"age"];
KVO 的工作流程
-
添加观察者:
- 调用
addObserver:forKeyPath:options:context:
方法时,KVO 会动态创建子类并重写 setter 方法。 - 原始对象的类指针(
isa
指针)被修改为新创建的子类。
- 调用
-
触发观察:
- 当属性值发生变化时,调用重写后的 setter 方法。
- 先触发
willChangeValueForKey:
,然后调用原始 setter 方法更新属性值,最后触发didChangeValueForKey:
。 - 触发
didChangeValueForKey:
时,会通知所有观察者属性值已经改变。
-
移除观察者:
- 调用
removeObserver:forKeyPath:
方法时,KVO 会将类指针恢复为原始类,并移除重写的 setter 方法。
- 调用
注意事项
- 自动 KVO:KVO 默认仅支持通过 setter 方法修改属性值的情况。直接修改实例变量不会触发 KVO。
- 手动触发 KVO :如果需要手动触发 KVO,可以调用
willChangeValue(forKey:)
和didChangeValue(forKey:)
方法。
objc
[self willChangeValueForKey:@"age"];
_age = newValue;
[self didChangeValueForKey:@"age"];
总结
KVO 是 iOS 中基于动态特性实现的观察机制,通过动态子类化和方法重写实现。当属性值变化时,KVO 会通知所有注册的观察者。这一机制使得对象间的通信更加灵活和高效,但也需要注意在使用过程中正确添加和移除观察者,以避免内存泄漏或崩溃。