原文:iOS面试题大全
KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于 KVC 实现的关键技术之一。
键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey:
和 didChangevlueForKey:
。在一个被观察属性发生改变之前,willChangeValueForKey:
一定会被调用,这就会记录旧的值。而当改变发生后,observeValueForKey:ofObject:change:context:
和 didChangeValueForKey:
也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。
@property (nonatomic, strong) NSDate *now;
- (void)viewDidLoad {
[super viewDidLoad];
_now = [NSDate date];
[self addObserver:self forKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"1");
[self willChangeValueForKey:@"now"]; // 手动触发 self.now 的 KVO,必写。
NSLog(@"2");
[self didChangeValueForKey:@"now"]; // 手动触发 self.now 的 KVO,必写。
NSLog(@"4");
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"3");
}
打印顺序是:1 2 3 4。从这里看顺序似乎是 wilChangeValueForKey:
、observeValueForKeyPath:ofObject:change:context:
、didChangeValueForKey:
。其实,实际情况是:wilChangeValueForKey:
先调用,接着是调用 didChangeValueForKey:
,在 didChangeValueForKey: 内部调用了 observeValueForKeyPath:ofObject:change:context:
。你可以注释掉[self didChangeValueForKey:@"now"];
试试。
但是平时我们一般不会这么干,我们都是等系统去“自动触发”。“自动触发”的实现原理:
比如调用 setNow: 时,系统还会以某种方式在中间插入 wilChangeValueForKey:
、didChangeValueForKey:
和 observeValueForKeyPath:ofObject:change:context:
的调用。
大致表现如下:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"];
[super setValue:aDate forKey:@"now"];
[self didChangeValueForKey:@"now"];
}
Apple 使用了 isa 混写(isa-swizzling)来实现 KVO,这种继承和方法注入是在运行时而不是编译时实现的。这就是正确命名如此重要的原因。只有在使用 KVC 命名约定时,KVO 才能做到这一点。KVO 在实现中通过 isa 混写(isa-swizzling)把这个对象的 isa 指针(isa 指针告诉 Runtime 系统这个对象的类是什么)指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。Apple 还重写、覆盖了 -class 方法并返回原来的类,企图欺骗我们:这个类没有变,就是原本那个类。
网友评论