前言
KVO
即:Key-Value Observing,它提供一種機(jī)制待笑,當(dāng)指定的對(duì)象的屬性被修改后鸣皂,則對(duì)象就會(huì)接受到通知。簡單的說就是每次指定的被觀察的對(duì)象的屬性被修改后,KVO就會(huì)自動(dòng)通知相應(yīng)的觀察者寞缝。
用法&原理
KVO實(shí)現(xiàn)原理很多博客都有很詳細(xì)的介紹,這里我就不再重復(fù)的闡述了,推薦幾篇博文供大家學(xué)習(xí).
來自sunnyxx
大神的博客: objc kvo簡單探索
來自objc中國
的文章: KVC 和 KVO
正序
實(shí)現(xiàn)KVO的思路
在了解完KVO的原理過后,通過分析得出,大體上的實(shí)現(xiàn)思路是這樣的:
- 當(dāng)注冊(cè)一個(gè)觀察者時(shí),首先要?jiǎng)討B(tài)創(chuàng)建被監(jiān)聽對(duì)象的類的一個(gè)派生類(子類)
- 將原類的實(shí)例變量的isa指針指向新創(chuàng)建的派生類
- 為了"混淆視聽",偽裝生成了派生類,重寫class方法,使其返回派生類的父類(也就是被監(jiān)聽對(duì)象的類)
- 重寫派生類的setter方法,在里面通知被監(jiān)聽的值的改變
具體核心代碼實(shí)現(xiàn)(部分)
添加觀察者
// 獲取類&類名
Class class = object_getClass(self);
NSString *className = NSStringFromClass(class);
// 判斷是否已經(jīng)生成KVO的派生類(前綴判斷)
if (![className hasPrefix:TFKVOClassPrefix]) {
// 生成派生類
class = [self creatNewClassWithInitialClass:className];
// 設(shè)置對(duì)象的類為生成的派生類
object_setClass(self, class);
}
// 判斷是否已經(jīng)實(shí)現(xiàn)重寫了set方法
if (![self hasSelector:setterSelector]) {
const char *types = method_getTypeEncoding(setterMethod);
// 重寫set方法添加監(jiān)聽
class_addMethod(class, setterSelector, (IMP)kvo_setter, types);
}
// 動(dòng)態(tài)給注冊(cè)者綁定數(shù)組,數(shù)組里面包含KVO信息(觀察的observer,key,block)
TFObservationInfo *info = [[TFObservationInfo alloc] initWithObserver:observer Key:key block:block];
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(TFKVOAssociatedObserverKey));
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void *)(TFKVOAssociatedObserverKey), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:info];
動(dòng)態(tài)創(chuàng)建派生類具體實(shí)現(xiàn)方法
Class cls = NSClassFromString(KvoClassName);
if (cls) { // 如果已經(jīng)存在新創(chuàng)建的派生類,直接返回
return cls;
}
Class initialClass = object_getClass(self);
// 動(dòng)態(tài)創(chuàng)建類
Class kvoClass = objc_allocateClassPair(initialClass, KvoClassName.UTF8String, 0);
// 得到類的實(shí)例方法
Method classMethod = class_getInstanceMethod(kvoClass, @selector(class));
// 獲取方法的Type字符串(包含參數(shù)類型和返回值類型)
const char *types = method_getTypeEncoding(classMethod);
// 重寫class方法
class_addMethod(kvoClass, @selector(class), (IMP)kvo_class, types);
// 注冊(cè)創(chuàng)建的類
objc_registerClassPair(kvoClass);
重寫setter方法
// 構(gòu)建 objc_super 的結(jié)構(gòu)體
struct objc_super superclass = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 向父類發(fā)送set消息
void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
objc_msgSendSuperCasted(&superclass, _cmd, newValue);
// 調(diào)用完后,獲取綁定的info,調(diào)用block回調(diào)
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(TFKVOAssociatedObserverKey));
for (TFObservationInfo *info in observers) {
if ([info.key isEqualToString:getterName]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
info.block(self, getterName, oldValue, newValue);
});
}
}
移除觀察者
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(TFKVOAssociatedObserverKey));
TFObservationInfo *removeInfo;
for (TFObservationInfo* info in observers) {
if (info.observer == observer && [info.key isEqual:key]) {
removeInfo = info;
break;
}
}
[observers removeObject:removeInfo];
完整實(shí)現(xiàn)源碼&詳細(xì)注釋&測試DEMO
尾言
就筆者而言,在實(shí)際開發(fā)中,很少使用KVO提供的API,雖然他很強(qiáng)大,但是還有一些不足.比如,你只能通過重寫 -observeValueForKeyPath:ofObject:change:context:
方法來獲得通知,想要自定義方法或者使用block實(shí)現(xiàn),都是不允許的.而且你還要處理父類的情況,例如父類同樣監(jiān)聽同一個(gè)對(duì)象的同一個(gè)屬性癌压。雖然context
這個(gè)參數(shù)就是干這個(gè)的,也可以解決這個(gè)問題 - 在 -addObserver:forKeyPath:options:context:
傳進(jìn)去一個(gè)父類不知道的context
,但是也是很麻煩的一件事,不是嗎?
有不少人都覺得官方 KVO 不好使的荆陆。Mike Ash 的
Key-Value Observing Done Right,以及獲得不少分享討論的KVO Considered Harmful都把KVO"批判"了一把滩届。所以在實(shí)際開發(fā)中 KVO 使用的情景并不多,更多時(shí)候還是用 Delegate 或 NotificationCenter吧被啼。
完結(jié).