从官方文档来武装一下自己(游击队->正规军)
原理说明
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. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
- 当对一个类添加观察后,这个类的 isa 指针被指向了一个中间类,而非真实的了类
关于 KVO 内部实现的原理,官方也就一句话带过
而这个中间类和 isa 就是 KVO 的核心了
为什么要实现一个中间类呢?
KVO的核心在于,属性的改变的时候,可以兼容的到
我们一般在更改属性值的时候一般都是
- 点方法 -> setter 方法
- setValueForKey
- 直接赋值
对于直接赋值,这个属于直接修改了指针的指向,这个就很难抓到了
所以重点看 setter 方法 和 setValueForKey 这两个方法
于是 KVO 就要在运行的时候动态的兼容我们的 setter 方法
那么苹果的实现方法就是,运行时新建了一个被观察的对象的子类
将被观察对象的 isa 指针指向子类
isa 略作说明
每个对象都有 isa 指针,isa 指针存储了一个类所有的信息。
例如:所有的方法、所有的属性
换句话来说,我们对 OC 类的操作,底层都是对 isa 指针的操作
例如: isKindOf 方法
其源码可以看出
对象 -> isa -> isa_class -> isa_super_class 直到找到 isa 类型相等
没有找到则 return false,找到则 return true
子类重写了 setter 方法,然后就可以抓到属性的改变了
至于怎么通知给观察者改变的,看下面的 官方文档重点翻译
官方文档重点翻译
- OS X 中,model and controller layers 很大程度省依赖 KVO
if your objects inherit from NSObject and you create properties in the usual way, your objects and their properties will automatically be KVO Compliant. It is also possible to implement compliance manually
- 继承自 NSObject 的类,常规的方式创建的属性,都自动的可以使用 KVO
Not all classes are KVO-compliant for all properties
- 并不是所有的属性都兼容 KVO
Manual change notification provides additional control over when notifications are emitted, and requires additional coding. You can control automatic notifications for properties of your subclass by implementing the class method [automaticallyNotifiesObserversForKey:](https://developer.apple.com/documentation/objectivec/nsobject/1409370-automaticallynotifiesobservers)
.
- 重写子类的 automaticallyNotifiesObserversForKey: 可以来控制属性的改变是否发送 KVO 通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
- 触发 KVO
// Call the accessor method.
[account setName:@"Savings"];
// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];
// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];
// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
- 重写 setter 方法 发送更改通知
- (void)setBalance:(double)theBalance {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
[self willChange:NSKeyValueChangeRemoval
valuesAtIndexes:indexes forKey:@"transactions"];
// Remove the transaction objects at the specified indexes.
[self didChange:NSKeyValueChangeRemoval
valuesAtIndexes:indexes forKey:@"transactions"];
}
网友评论