45. addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?
// 添加键值观察
/*
1 观察者,负责处理监听事件的对象
2 观察的属性
3 观察的选项
4 上下文
*/
[self.person addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:@"Person Name"];
observer中需要实现该方法:
// 所有的 kvo 监听到事件,都会调用此方法
/*
1. 监听的被观察者的属性键路径
2. 被观察者对象
3. change 被观察者对象属性变化字典(新/旧)
4. 上下文,与监听的时候传递的一致
*/
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
46. 如何手动触发一个value的KVO
自动触发是指类似这种场景:在注册 KVO 之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了。
自动触发 KVO 的原理
键值观察通知依赖于 NSObject
的两个方法:
willChangeValueForKey:
和
didChangevlueForKey:
在一个被观察属性发生改变之前, willChangeValueForKey:
一定会被调用,这就 会记录旧的值。而当改变发生后,
observeValueForKey:ofObject:change:context:
会被调用,继而 didChangeValueForKey:
也会被调用。如果可以手动实现这些调用,就可以实现“手动触发KVO”了。
//
// ViewController.m
// 46
//
// Created by zhaoyingxin on 16/8/22.
// Copyright © 2016年 [email protected]. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSDate *now;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self manualKVO];
}
- (void)manualKVO{
[self addObserver:self
forKeyPath:@"now"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:@"self.now.kvo"];
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 *)change
context:(void *)context {
NSLog(@"3");
NSLog(@"keyPath = %@",keyPath);
NSLog(@"object = %@",object);
NSLog(@"change = %@",change);
NSLog(@"context = %@",context);
}
//2016-08-22 11:45:45.805 46[44377:802802] 1
//2016-08-22 11:45:45.805 46[44377:802802] 2
//2016-08-22 11:45:45.806 46[44377:802802] 3
//2016-08-22 11:45:45.806 46[44377:802802] keyPath = now
//2016-08-22 11:45:45.806 46[44377:802802] object =
//2016-08-22 11:45:45.806 46[44377:802802] change = {
// kind = 1;
// new = "";
// old = "";
//}
//2016-08-22 11:45:45.806 46[44377:802802] context = self.now.kvo
//2016-08-22 11:45:45.807 46[44377:802802] 4
@end
“自动触发”的实现原理:
比如调用 setNow:
时,系统还会以某种方式在中间插入 willChangeValueForKey:
、 didChangeValueForKey:
和 observeValueForKeyPath:ofObject:change:context:
的调用。
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"]; // 没有必要
_now = aDate;
[self didChangeValueForKey:@"now"]; // 没有必要
}
这完全没有必要,不要这么做,这样的话,KVO代码会被调用两次
。KVO在调用存取方法之前总是调用 willChangeValueForKey:
,之后总是调用 didChangeValueForkey:
。怎么做到的呢?答案是通过 isa 混写(isa-swizzling)
参考苹果官方文档:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-SW3
48.KVC的keyPath中的集合运算符如何使用?
1.必须用在集合对象上或普通对象的集合属性上
2.简单集合运算符有@avg, @count , @max , @min ,@sum,
3.格式 @"@sum.age"或 @"集合属性[email protected]"
49. KVC和KVO的keyPath一定是属性么?
KVO支持实例变量
51.apple用什么方式实现对一个对象的KVO?
Apple 的文档对 KVO 实现的描述:
Automatic key-value observing is implemented
using a technique called
isa-swizzling...
When an observer is registered for an attribute of an object
the isa pointer of the observed object is modified,
pointing to an intermediate class rather than at the true class ...
从Apple 的文档可以看出:Apple 并不希望过多暴露 KVO 的实现细节。不过,要是借助 runtime 提供的方法去深入挖掘,所有被掩盖的细节都会显示:
当给一个对象添加观察者时,一个新的类会被动态创建。
这个类继承自该对象原本的类,并重写了被观察属性的 setter 方法。
重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察者对象:属性值的更改。
最后通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针
( isa 指针告诉 Runtime 系统这个对象的类是什么 )
指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。
Apple 使用了 isa 混写(isa-swizzling)来实现 KVO
把 didChangeValueForKey:
注释掉, observeValueForKeyPath:ofObject:change:context:
不会执行
observeValueForKeyPath:ofObject:change:context:
是在 didChangeValueForKey:
内部触发的操作
调用顺序
willChangeValueForKey:
---> didChangeValueForKey:
--->
observeValueForKeyPath:ofObject:change:context:
“手动触发”的使用场景是什么?一般我们只在希望能控制“回调的调用时机”时才会这么做。
而“回调的调用时机”就是在你调用 didChangeValueForKey:
方法时