KVO和runtime的關(guān)系

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末休偶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辜羊,更是在濱河造成了極大的恐慌踏兜,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件八秃,死亡現(xiàn)場(chǎng)離奇詭異碱妆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)昔驱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門疹尾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舍悯,你說我怎么就攤上這事航棱。” “怎么了萌衬?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵饮醇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我秕豫,道長(zhǎng)朴艰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任混移,我火速辦了婚禮祠墅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歌径。我一直安慰自己毁嗦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布回铛。 她就那樣靜靜地躺著狗准,像睡著了一般克锣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腔长,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天袭祟,我揣著相機(jī)與錄音,去河邊找鬼捞附。 笑死巾乳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸟召。 我是一名探鬼主播胆绊,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼药版!你這毒婦竟也來了辑舷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤槽片,失蹤者是張志新(化名)和其女友劉穎何缓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體还栓,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碌廓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了剩盒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谷婆。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辽聊,靈堂內(nèi)的尸體忽然破棺而出纪挎,到底是詐尸還是另有隱情,我是刑警寧澤跟匆,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布异袄,位于F島的核電站,受9級(jí)特大地震影響玛臂,放射性物質(zhì)發(fā)生泄漏烤蜕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一迹冤、第九天 我趴在偏房一處隱蔽的房頂上張望讽营。 院中可真熱鬧,春花似錦泡徙、人聲如沸橱鹏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚀瘸。三九已至狡蝶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贮勃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工苏章, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寂嘉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓枫绅,卻偏偏與公主長(zhǎng)得像泉孩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子并淋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345