什么是KVO
官方文档
Key-value observing是一种允许对象在其他对象的指定属性发生变化时被通知的机制。
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
我们通常在最后一个参数context传递NULL,比如
[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
Context
A safer and more extensible approach is to use the context to ensure notifications you receive are destined for your observer and not a superclass.(更安全、更可扩展的方法是使用上下文来确保接收到的通知是发给观察者的,而不是超类) Listing 1 Creating context pointers
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
Listing 2 Registering the inspector as an observer of the balance and interestRate properties
- (void)registerAsObserverForAccount:(Account*)account {
[account addObserver:self
forKeyPath:@"balance"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountBalanceContext];
[account addObserver:self
forKeyPath:@"interestRate"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountInterestRateContext];
}
移除观察者
An observer does not automatically remove itself when deallocated. The observed object continues to send notifications, oblivious to the state of the observer. However, a change notification, like any other message, sent to a released object, triggers a memory access exception. You therefore ensure that observers remove themselves before disappearing from memory.(当被释放时,观察者不会自动移除自己。被观察的对象继续发送通知,无视观察者的状态。然而,与任何其他消息一样,发送到已释放对象的更改通知会触发内存访问异常。因此,您可以确保观察者在从记忆中消失之前将自己移走)
Automatic Change Notification自动
一对多的情况:比如下载视频的进度 writte和total都会变化
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
Manual Change Notification 手动
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return NO;
}
- (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
对可变数组的观察
上一节KVC有说明 The protocol defines three different proxy methods for collection object access, each with a key and a key path variant: mutableArrayValueForKey: and mutableArrayValueForKeyPath:These return a proxy object that behaves like an NSMutableArray object. mutableSetValueForKey: and mutableSetValueForKeyPath:These return a proxy object that behaves like an NSMutableSet object. mutableOrderedSetValueForKey: and mutableOrderedSetValueForKeyPath:These return a proxy object that behaves like an NSMutableOrderedSet object.
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
KVO底层原理
添加观察者时:
移除观察者时:
我们发现在添加了KVO观察者之后,
self.person指向了一个NSKVONotifying_Person,在移除观察者的时候,isa又指回来了(但是创建的NSKVONotifying_Person类并没有被销毁)。我们可以猜测这个是在运行时动态生成。那么这个NSKVONotifying_Person和LGPerson有啥关系呢?
#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
// 注册类的总数
int count = objc_getClassList(NULL, 0);
// 创建一个数组, 其中包含给定对象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
// 获取所有已注册的类
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i<count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[mArray addObject:classes[i]];
}
}
free(classes);
NSLog(@"classes = %@", mArray);
}
我们在给Person添加观察者之前和之后,分别打印下当前的Person的子类;
[self printClasses:[Person class]];
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
[self printClasses:[Person class]];
可以得到结论:当前的
NSKVONotifying_Person是Person的子类
NSKVONotifying_Person里面包含了什么
对于一个类来说就那么几个关键的东西:属性、方法、协议
#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
[self printClassAllMethod:objc_getClass("NSKVONotifying_Person")];
重写了这四个方法:
setNickName - class - dealloc - _isKVOA
KVO观察的到底是成员变量还是属性 ivar VS Property
虽然NSKVONotifying_Person重写了set方法,但是我们还有有一个疑惑,观察者到底观察的是成员变量还是属性呢?继续验证
@interface Person : NSObject{
@public
NSString *name;
}
@property (nonatomic, copy) NSString *nickName;
@end
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
打印输出:
结论:并不会观察到成员变量。
setter方法监听
通过上面我们知道KVO实际上对set方法进行观察,set方法做了什么。使用LLDB调试
当观察到变更的时候就会hit,此时
bt一下
在
touchesBegin到setNickName之间系统调用了三个方法:
1.-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
2.-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
3._NSSetObjectValueAndNotify
点击查看调用栈内的汇编代码大致可以得出一个流程:
在我们开始监听的时候,生成了一个NSKVONotifying_Person,同时生成了4个方法。当我们set赋值的时候,此时的set其实是NSKVONotifying_Person的set方法,此时在方法3中经过一些处理willChangeValueForKey:和didChangeValueForKey:修改完之后回调通知就来了。