1寓免、KVO是什么胸梆?
在Objc中有一種觀察者模式,即是Key Value Observing(KVO)扫责。利用KVO可以很容易實(shí)現(xiàn)視圖組件和數(shù)據(jù)模型的分離榛鼎。當(dāng)數(shù)據(jù)模型的值改變時(shí),會(huì)馬上觸發(fā)視圖組件鳖孤,更新視圖組件者娱。在Objc中要實(shí)現(xiàn)KVO,必須實(shí)現(xiàn)NSKeyValueObServing協(xié)議苏揣,所幸的是NSObject已經(jīng)實(shí)現(xiàn)該協(xié)議黄鳍,也就是說,幾乎所有的Objc對(duì)象都可以使用KVO平匈。
2框沟、在OC中,KVO的使用步驟一般是:
被監(jiān)聽者通過 addObserver:forKeyPath:options:context: 方法吐葱,添加監(jiān)聽
監(jiān)聽者重寫 observeValueForKeyPath:ofObject:change:context: 方法街望,實(shí)現(xiàn)監(jiān)聽
被監(jiān)聽者通過 removeObserver: forKeyPath: context: 移除監(jiān)聽
KVO 行為是同步的 并且發(fā)生與所觀察的值發(fā)生變化的同樣的線程上。這聽起來有點(diǎn)拗口弟跑,簡(jiǎn)單點(diǎn)說灾前,就是監(jiān)聽行為發(fā)生的線程和所觀察的值發(fā)生變化的線程,肯定是同一個(gè)線程孟辑,這樣我們使用的時(shí)候就需要注意了:
當(dāng)我們?cè)噲D從其他線程改變屬性值的時(shí)候我們應(yīng)當(dāng)十分小心哎甲,除非能確定所有的觀察者都用線程安全的方法處理 KVO 通知
通常來說,我們不推薦把 KVO 和多線程混起來饲嗽。如果我們要用多個(gè)隊(duì)列和線程炭玫,我們不應(yīng)該在它們互相之間用 KVO。
3貌虾、和runtime的關(guān)系:
一個(gè)對(duì)象在被調(diào)用addObserver方法時(shí)吞加,會(huì)在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建一個(gè)KVO前綴的原類的子類,用來重寫所有的setter方法尽狠,并且該子類的- (Class) class和- (Class) superclass方法會(huì)被重寫衔憨,返回父類(原始類)的Class。最后會(huì)將當(dāng)前對(duì)象的類改為這個(gè)KVO前綴的子類袄膏。被觀察對(duì)象的 isa 指針會(huì)指向一個(gè)中間類践图,而不是原來真正的類。
比較繞沉馆,讓我們來看個(gè)例子码党。
比如說類Person的實(shí)例person調(diào)用了addObserver方法時(shí)德崭,addObserver方法內(nèi)部給你創(chuàng)建了一個(gè)KVOPerson類,KVOPerson的所有的setter方法會(huì)被重寫揖盘,它的class和superClass方法會(huì)被改寫成返回Person和NSObject眉厨,之后使用object_setClass將KVOPerson設(shè)置成person的class。
當(dāng)我們調(diào)用person的setName方法時(shí)扣讼,實(shí)際是調(diào)用的一個(gè)KVOPerson實(shí)例的setName方法缺猛,但由于重寫了class,在外部看不出來其中的差別椭符。在setter方法中荔燎,我們?cè)趯?shí)際值被改變的前后回調(diào)用- (void)willChangeValueForKey:(NSString *)key;和- (void)didChangeValueForKey:(NSString *)key;方法,通知觀察者值的變化销钝。
4有咨、具體實(shí)現(xiàn):
(1)檢查對(duì)象的類有沒有相應(yīng)的 setter 方法。如果沒有拋出異常蒸健;
(2)檢查對(duì)象 isa 指向的類是不是一個(gè) KVO 類座享。如果不是,新建一個(gè)繼承原來類的子類似忧,并把 isa 指向這個(gè)新建的子類渣叛;
(3)檢查對(duì)象的 KVO 類重寫過沒有這個(gè) setter 方法。如果沒有盯捌,添加重寫的 setter 方法淳衙;
(4)添加這個(gè)觀察者
NSString *const kKVOClassPrefix = @"KVOClassPrefix_";
NSString *const kKVOAssociatedObservers = @"KVOAssociatedObservers";
-
(void)CC_addObserver:(NSObject *)observer
forKey:(NSString )key
withBlock:(PGObservingBlock)block
{
/
一、 檢查對(duì)象的類有沒有相應(yīng)的setter方法饺著,如果沒有拋出異常具體細(xì)節(jié):
1.1)箫攀、先通過 setterForGetter() 方法獲得相應(yīng)的 setter 的名字(SEL)。
1.2)幼衰、把key的首字母大寫靴跛; 前面加上set; key就變成了setKey渡嚣。
1.3)梢睛、再用class_getInstanceMethod去獲得setKey:的實(shí)現(xiàn)(Method),如果沒有识椰,自然要拋出異常
*/
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;
}/*
二扬绪、檢查對(duì)象isa指向的類是不是一個(gè)KVO類。如果不是裤唠,新建一個(gè)繼承原來類的子類,并把isa指向這個(gè)新建的子類
*/Class clazz = object_getClass(self);
NSString *clazzName = NSStringFromClass(clazz);// 2.1)莹痢、先看類名有沒有我們定義的前綴种蘸。如果沒有墓赴,我們就去創(chuàng)建新的子類
if (![clazzName hasPrefix:kKVOClassPrefix])
{
clazz = [self makeKvoClassWithOriginalClassName:clazzName];
object_setClass(self, clazz);
}
// 2.2)、到這里為止, object的類已不是原類了, 而是KVO新建的類
// 2.3)航瞭、例如官方的API: Person -> NSKVONotifying_Person()
// 2.4)诫硕、kKVOClassPrefix是自己定義的一個(gè)宏,便于區(qū)分系統(tǒng)
/*
三刊侯、重寫setter方法章办。新的setter在調(diào)用原setter方法后,通知每個(gè)觀察者(調(diào)用之前傳入的block)
*/
if (![self hasSelector:setterSelector])
{
const char *types = method_getTypeEncoding(setterMethod);
class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
}
/*
四滨彻、把這個(gè)觀察的相關(guān)信息存在associatedObject里藕届。
具體相關(guān):
觀察的相關(guān)信息(觀察者,被觀察的 key, 和傳入的 block )封裝在 CCObservationInfo 類里亭饵。
*/
CCObservationInfo *info = [[CCObservationInfo alloc] initWithObserver:observer Key:key block:block];
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kKVOAssociatedObservers));
if (!observers)
{
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void *)(kKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:info];
}
詳解及demo:http://www.reibang.com/p/a32d851603e5
牛X的參考:http://tech.glowing.com/cn/implement-kvo/
牛X的應(yīng)用:http://www.reibang.com/p/742b4b248da9