iOS面试题:如何手动触发一个 value 的 KVO?

96
iOS猿_员
0.1 2019.01.29 20:59* 字数 522

原文: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 方法并返回原来的类,企图欺骗我们:这个类没有变,就是原本那个类。

iOS面试题小集
Web note ad 1