一、KVO 實(shí)現(xiàn)機(jī)制
Apple 的文檔有簡(jiǎn)單提到過 KVO 的實(shí)現(xiàn):
Key-Value Observing Implementation Details
Automatic key-value observing is implemented using a technique called *isa-swizzling*.
The `isa` pointer, as the name suggests, points to the object's class which maintains a dispatch table.
This dispatch table essentially contains pointers to the methods the class implements, among other data.
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.
- 被觀察對(duì)象的 isa 指針會(huì)指向一個(gè)中間類,而不是原來真正的類
- Mike Ash 早在 2009 年就做了這么個(gè)探究。
- 觀察一個(gè)對(duì)象時(shí),一個(gè)新的類會(huì)動(dòng)態(tài)被創(chuàng)建;
- 這個(gè)類繼承自該對(duì)象的原本的類,并重寫了被觀察屬性的 setter 方法;
- 重寫的 setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察對(duì)象值的更改;
- 把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類;
- 完成新創(chuàng)建的子類的實(shí)例。
二、KVO 缺陷
- 比如,你只能通過重寫 -observeValueForKeyPath:ofObject:change:context: 方法來獲得通知;
- 想要提供自定義的 selector ,不行;
- 想要傳一個(gè) block ,不行;
- 父類同樣監(jiān)聽同一個(gè)對(duì)象的同一個(gè)屬性 ->還要處理父類的情況 ;
- 有時(shí)候,不知道父類是不是對(duì)這個(gè)消息有興趣。-addObserver:forKeyPath:options:context: 傳進(jìn)去一個(gè)父類不知道的 context
注:有不少人都覺得官方 KVO 不好使的。
- Mike Ash 的 Key-Value Observing Done Right;
- 以及獲得不少分享討論的 KVO Considered Harmful ;
- 所以在實(shí)際開發(fā)中 KVO 使用的情景并不多,更多時(shí)候還是用 Delegate 或 NotificationCenter。
三、自己實(shí)現(xiàn) KVO
- 1、創(chuàng)建 NSObject 的 Category,并在頭文件中添加兩個(gè) API:
- (void)PG_addObserver:(NSObject *)observer
forKey:(NSString *)key
withBlock:(PGObservingBlock)block
{
SEL setterSelector = NSSelectorFromString(setterForGetter(key));
Method setterMethod = class_getInstanceMethod([self class], setterSelector);
if (!setterMethod) {
NSString *reason = [NSString stringWithFormat:@"Object %@ does not have a setter for key %@", self, key];
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:reason
userInfo:nil];
return;
}
Class clazz = object_getClass(self);
NSString *clazzName = NSStringFromClass(clazz);
// if not an KVO class yet
if (![clazzName hasPrefix:kPGKVOClassPrefix]) {
clazz = [self makeKvoClassWithOriginalClassName:clazzName];
object_setClass(self, clazz);
}
// add our kvo setter if this class (not superclasses) doesn't implement the setter?
if (![self hasSelector:setterSelector]) {
const char *types = method_getTypeEncoding(setterMethod);
class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
}
PGObservationInfo *info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block];
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:info];
}
- 檢查對(duì)象的類有沒有相應(yīng)的 setter 方法。如果沒有拋出異常;
- 檢查對(duì)象 isa 指向的類是不是一個(gè) KVO 類。如果不是,新建一個(gè)繼承原來類的子類,并把 isa 指向這個(gè)新建的子類;
- 檢查對(duì)象的 KVO 類重寫過沒有這個(gè) setter 方法。如果沒有,添加重寫的 setter 方法;
- 添加這個(gè)觀察者
- 通過 object_setClass() 修改 isa 指針
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
Class clazz = NSClassFromString(kvoClazzName);
if (clazz) {
return clazz;
}
// class doesn't exist yet, make it
Class originalClazz = object_getClass(self);
Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
// grab class method's signature so we can borrow it
Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
objc_registerClassPair(kvoClazz);
return kvoClazz;
}
- 重寫 setter 方法。新的 setter 在調(diào)用原 setter 方法后,通知每個(gè)觀察者(調(diào)用之前傳入的 block )
static void kvo_setter(id self, SEL _cmd, id newValue)
{
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterForSetter(setterName);
if (!getterName) {
NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:reason
userInfo:nil];
return;
}
id oldValue = [self valueForKey:getterName];
struct objc_super superclazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// cast our pointer so the compiler won't complain
void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
// call super's setter, which is original class's setter method
objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
// look up observers and call the blocks
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
for (PGObservationInfo *each in observers) {
if ([each.key isEqualToString:getterName]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
each.block(self, getterName, oldValue, newValue);
});
}
}
}
- 觀察的相關(guān)信息存在 associatedObject 里。觀察的相關(guān)信息(觀察者,被觀察的 key, 和傳入的 block )封裝在 PGObservationInfo 類里
@interface PGObservationInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) PGObservingBlock block;
@end
參考:
完整的例子可以從這里下載:ImplementKVO
KVO Implementation
Creating Classes at Runtime in Objective-C