2018-06-04

談一談你對KVO的理解?

A:添加響應(yīng)者,監(jiān)聽對象變化,當(dāng)對象改變時(shí)調(diào)用代理。

B:動(dòng)態(tài)創(chuàng)建NSKVONotifying_XX類,修改被監(jiān)聽對象isa指針指向,只要調(diào)用對象的set方法,

就會(huì)調(diào)用NSKVONotifying_XX的set方法。本質(zhì):判斷對象的set方法有沒有被調(diào)用

二、蘋果官方文檔描述

Automatic key-value observing is implemented using a technique called isa-swizzling... 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 ..

以上文檔的基本內(nèi)容是:

“當(dāng)對象注冊觀察者時(shí),對象的ISA指針被修改,指向中間類,而不是原來真正的類...”,這是蘋果官方給出的文檔。聽到是不是很懵?沒關(guān)系,未了解之前,我和你一樣,看了這篇文章后你將和我一樣(小裝一下,勿噴)。

舉例說明:(監(jiān)聽一個(gè)Person類底層實(shí)現(xiàn))

2.1)、動(dòng)態(tài)創(chuàng)建NSKVONotifying_Person,NSKVONotifying_Person是Person子類,做KVO;

2.2)、修改當(dāng)前對象的isa指針->NSKVONotifying_Person;

2.3)、只要調(diào)用對象的set,就會(huì)調(diào)用NSKVONotifying_Person的set方法;

2.4)、重寫NSKVONotifying_Person的set方法,1[super set:] 2、通知觀察者,告訴屬性改變。

三、核心代碼

/**

?添加觀察者?(addObserver:?forKeyPath:?options:?context:)

?@param?observer?觀察者

?@param?keyPath?監(jiān)聽對象屬性

?@param?options?屬性配置

?@param?context?上下文

?*/

-?(void)addObserver:(NSObject?*)observer?forKeyPath:(NSString?*)keyPath

????????????options:(NSKeyValueObservingOptions)options

????????????context:(void*)context

{

????/*

?????*?options

?????*

?????*?NSKeyValueObservingOptionNew???????//?change字典包括改變后的值

?????*?NSKeyValueObservingOptionOld???????//?change字典包括改變前的值

?????*?NSKeyValueObservingOptionInitial???//?注冊后立刻觸發(fā)KVO通知

?????*?NSKeyValueObservingOptionPrior?????//?值改變前是否也要通知(通知兩次)

?????*/

}

/**

?監(jiān)聽按鈕狀態(tài)改變的方法

?@param?keyPath?改變的屬性

?@param?object?被監(jiān)聽對象

?@param?change?改變后的數(shù)據(jù)

?@param?context?注冊監(jiān)聽時(shí)context傳遞過來的值

?*/

-?(void)observeValueForKeyPath:(NSString?*)keyPath

??????????????????????ofObject:(id)object

????????????????????????change:(NSDictionary?*)change

???????????????????????context:(void*)context

{

}

到此為止,這是我以前了解的所有KVO了,就這么點(diǎn)東西,用起來嗖嗖嗖啊。但是,大神永遠(yuǎn)是最騷的。前幾天看到一個(gè)大神覺得系統(tǒng)的KVO不好用,自定義了一個(gè),居然還是15年寫的,我勒個(gè)槽槽槽,如何自己動(dòng)手實(shí)現(xiàn) KVO,就是這篇文章,讀了半天,終于弄明白了,總結(jié)總結(jié)吧,別白看了。

首先,懷著虛榮的心先了解一下,為什么要自定義?對啊,蘋果給的多好用,干嘛還要自定義去,脫褲子放屁(多此一舉嘛),別著急,聽聽big borther 怎么說。

四、進(jìn)階

4.1、KVO 缺陷

big brother原話:

"KVO 很強(qiáng)大,沒錯(cuò)。知道它內(nèi)部實(shí)現(xiàn),或許能幫助更好地使用它,或在它出錯(cuò)時(shí)更方便調(diào)試。但官方實(shí)現(xiàn)的 KVO 提供的 API 實(shí)在不怎么樣。

比如,你只能通過重寫 -observeValueForKeyPath:ofObject:change:context: 方法來獲得通知。想要提供自定義的 selector ,不行;想要傳一個(gè) block ,門都沒有。而且你還要處理父類的情況 - 父類同樣監(jiān)聽同一個(gè)對象的同一個(gè)屬性。但有時(shí)候,你不知道父類是不是對這個(gè)消息有興趣。雖然 context 這個(gè)參數(shù)就是干這個(gè)的,也可以解決這個(gè)問題 - 在 -addObserver:forKeyPath:options:context: 傳進(jìn)去一個(gè)父類不知道的 context。但總覺得框在這個(gè) API 的設(shè)計(jì)下,代碼寫的很別扭。至少至少,也應(yīng)該支持 block 吧。

有不少人都覺得官方 KVO 不好使的。Mike Ash 的 Key-Value Observing Done Right,以及獲得不少分享討論的 KVO Considered Harmful 都把 KVO 拿出來吊打了一番。所以在實(shí)際開發(fā)中 KVO 使用的情景并不多,更多時(shí)候還是用 Delegate 或 NotificationCenter。"

目的:自定義一個(gè)KVO

操作:創(chuàng)建 NSObject+KVO 類,封裝自定義API;

4.2、創(chuàng)建一個(gè)NSObject的Category,?.h文件中暴露兩個(gè)API,用于添加和刪除KVO。例:

#import//?宏定義

NSString?*constkPGKVOClassPrefix?=?@"PGKVOClassPrefix_";

NSString?*constkPGKVOAssociatedObservers?=?@"PGKVOAssociatedObservers";

/**?監(jiān)聽回調(diào)用block?*/

typedef?void(^PGObservingBlock)(id?observedObject,

????????????????????????????????NSString?*observedKey,

????????????????????????????????id?oldValue,?id?newValue);

@interfaceNSObject?(KVO)

/**?添加觀察者?*/

-?(void)PG_addObserver:(NSObject?*)observer

????????????????forKey:(NSString?*)key

?????????????withBlock:(PGObservingBlock)block;

/**?移除觀察者?*/

-?(void)PG_removeObserver:(NSObject?*)observer

???????????????????forKey:(NSString?*)key;

@end

.m內(nèi)部實(shí)踐

/*

?*?1、檢查對象的類有沒有相應(yīng)的?setter?方法。如果沒有拋出異常;

?*?2、檢查對象?isa?指向的類是不是一個(gè)?KVO?類。如果不是,新建一個(gè)繼承原來類的子類,并把?isa?指向這個(gè)新建的子類;

?*?3、檢查對象的?KVO?類重寫過沒有這個(gè)?setter?方法。如果沒有,添加重寫的?setter?方法;

?*?4、添加這個(gè)觀察者

?*/

-?(void)PG_addObserver:(NSObject?*)observer

????????????????forKey:(NSString?*)key

?????????????withBlock:(PGObservingBlock)block

{

????/*

?????一、?檢查對象的類有沒有相應(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;

????}


????/*

?????二、檢查對象isa指向的類是不是一個(gè)KVO類。如果不是,新建一個(gè)繼承原來類的子類,并把isa指向這個(gè)新建的子類

?????*/


????Class?clazz?=?object_getClass(self);

????NSString?*clazzName?=?NSStringFromClass(clazz);


????//?2.1)、先看類名有沒有我們定義的前綴。如果沒有,我們就去創(chuàng)建新的子類

????if(![clazzName?hasPrefix:kPGKVOClassPrefix])

????{

????????clazz?=?[self?makeKvoClassWithOriginalClassName:clazzName];

????????object_setClass(self,?clazz);

????}

????//?2.2)、到這里為止,?object的類已不是原類了,?而是KVO新建的類

????//?2.3)、例如官方的API:?Person?->?NSKVONotifying_Person()

????//?2.4)、kPGKVOClassPrefix是自己定義的一個(gè)宏,便于區(qū)分系統(tǒng)



????/*

?????三、重寫setter方法。新的setter在調(diào)用原setter方法后,通知每個(gè)觀察者(調(diào)用之前傳入的block)

?????*/

????if(![self?hasSelector:setterSelector])

????{

????????constchar?*types?=?method_getTypeEncoding(setterMethod);

????????class_addMethod(clazz,?setterSelector,?(IMP)kvo_setter,?types);

????}


????/*

?????四、把這個(gè)觀察的相關(guān)信息存在associatedObject里。


?????具體相關(guān):

????????觀察的相關(guān)信息(觀察者,被觀察的?key,?和傳入的?block?)封裝在?PGObservationInfo?類里。

?????*/

????PGObservationInfo?*info?=?[[PGObservationInfo?alloc]?initWithObserver:observer?Key:key?block:block];

????NSMutableArray?*observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));

????if(!observers)

????{

????????observers?=?[NSMutableArray?array];

????????objc_setAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers),?observers,?OBJC_ASSOCIATION_RETAIN_NONATOMIC);

????}

????[observers?addObject:info];

}

這就是自定義的核心代碼了,內(nèi)容注視都整理在里面,有幾個(gè)方法需要具體介紹一下:

4.3、核心方法解讀:

setterForGetter

getterForSetter

makeKvoClassWithOriginalClassName

kvo_class

PG_removeObserver

4.3.1、setterForGetter (name -> Name -> setName:)

/**?根據(jù)getter方法名獲得對應(yīng)的setter方法名?*/

staticNSString?*?setterForGetter(NSString?*getter)

{

????if(getter.length?<=?0)?{

????????returnnil;

????}


????//?upper?case?the?first?letter

????NSString?*firstLetter?=?[[getter?substringToIndex:1]?uppercaseString];

????NSString?*remainingLetters?=?[getter?substringFromIndex:1];


????//?add?'set'?at?the?begining?and?':'?at?the?end

????NSString?*setter?=?[NSString?stringWithFormat:@"set%@%@:",?firstLetter,?remainingLetters];


????returnsetter;

}

4.3.2、getterForSetter(setName: -> Name -> name)

/**?根據(jù)setter方法名獲得對應(yīng)的getter方法名?*/

staticNSString?*?getterForSetter(NSString?*setter)

{

????if(setter.length?<=0||?![setter?hasPrefix:@"set"]?||?![setter?hasSuffix:@":"])?{

????????returnnil;

????}


????//?remove?'set'?at?the?begining?and?':'?at?the?end

????NSRange?range?=?NSMakeRange(3,?setter.length?-?4);

????NSString?*key?=?[setter?substringWithRange:range];


????//?lower?case?the?first?letter

????NSString?*firstLetter?=?[[key?substringToIndex:1]?lowercaseString];

????key?=?[key?stringByReplacingCharactersInRange:NSMakeRange(0,?1)

???????????????????????????????????????withString:firstLetter];?

????returnkey;

}

4.3.3、makeKvoClassWithOriginalClassName

-?(Class)makeKvoClassWithOriginalClassName:(NSString?*)originalClazzName

{

????//?生成kPGKVOClassPrefix_class的類名

????NSString?*kvoClazzName?=?[kPGKVOClassPrefix?stringByAppendingString:originalClazzName];

????Class?clazz?=?NSClassFromString(kvoClazzName);


????//?如果kvo?class已經(jīng)被注冊過了,?則直接返回

????if(clazz)?{

????????returnclazz;

????}


????/*

?????*??如果kvo?class不存在,?則創(chuàng)建這個(gè)類

?????*??class?doesn't?exist?yet,?make?it

?????*/

????Class?originalClazz?=?object_getClass(self);

????Class?kvoClazz?=?objc_allocateClassPair(originalClazz,?kvoClazzName.UTF8String,?0);


????/*

?????*??修改kvo?class方法的實(shí)現(xiàn),?學(xué)習(xí)Apple的做法,?隱瞞這個(gè)kvo_class

?????*??grab?class?method's?signature?so?we?can?borrow?it

?????*/

????Method?clazzMethod?=?class_getInstanceMethod(originalClazz,?@selector(class));

????constchar?*types?=?method_getTypeEncoding(clazzMethod);

????class_addMethod(kvoClazz,?@selector(class),?(IMP)kvo_class,?types);


????//?注冊kvo_class

????objc_registerClassPair(kvoClazz);


????returnkvoClazz;

}

4.3.4、kvo_class

1

2

3

4

staticClass?kvo_class(id?self,?SEL?_cmd)

{

????returnclass_getSuperclass(object_getClass(self));

}

4.3.5、kvo_setter

#pragma?mark?-?Overridden?Methods

/**?重寫setter方法,?新方法在調(diào)用原方法后,?通知每個(gè)觀察者(調(diào)用傳入的block)?*/

staticvoidkvo_setter(id?self,?SEL?_cmd,?id?newValue)

{

????NSString?*setterName?=?NSStringFromSelector(_cmd);

????NSString?*getterName?=?getterForSetter(setterName);


????//?如果不存在getter方法

????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];


????//?調(diào)用原類的setter方法

????struct?objc_super?superclazz?=?{

????????.receiver?=?self,

????????.super_class?=?class_getSuperclass(object_getClass(self))

????};

????/*

?????*???這里需要做個(gè)類型強(qiáng)轉(zhuǎn),?否則會(huì)報(bào)too?many?argument的錯(cuò)誤

?????*???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);


????/*

?????*??找出觀察者的數(shù)組,?調(diào)用對應(yīng)對象的callback

?????*??look?up?observers?and?call?the?blocks

?????*/

????NSMutableArray?*observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));

?????//?遍歷數(shù)組

????for(PGObservationInfo?*eachinobservers)

????{

????????if([each.key?isEqualToString:getterName])

????????{

????????????//?gcd異步調(diào)用callback

????????????dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{

????????????????each.block(self,?getterName,?oldValue,?newValue);

????????????});

????????}

????}

}

4.3.6、PG_removeObserver

/**?移除觀察者?*/

-?(void)PG_removeObserver:(NSObject?*)observer?forKey:(NSString?*)key

{

????NSMutableArray*?observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));

????PGObservationInfo?*infoToRemove;

????for(PGObservationInfo*?info?inobservers)

????{

????????if(info.observer?==?observer?&&?[info.key?isEqual:key])

????????{

????????????infoToRemove?=?info;

????????????break;

????????}

????}

????[observers?removeObject:infoToRemove];

}

4.3.7、調(diào)用

[self.message?PG_addObserver:self?forKey:NSStringFromSelector(@selector(text))?withBlock:^(id?observedObject,?NSString?*observedKey,?id?oldValue,?id?newValue)?{

}];

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評(píng)論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,904評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,368評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,736評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,919評(píng)論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,235評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,427評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,656評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評(píng)論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,160評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,380評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,755評(píng)論 0 9
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,499評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,125評(píng)論 1 32
  • 買不起那些亂七八糟的貼紙膠帶,就自己動(dòng)手畫貼紙,然后復(fù)印再上色! 不會(huì)畫畫也沒關(guān)系從網(wǎng)上下載小圖彩色打印!然后開始...
    兔子姐姐愛畫畫閱讀 1,438評(píng)論 2 13
  • 陌生的城市 錯(cuò)亂的腳步 不安的心 東望望 西看看 忽閃忽閃眼睛 神秘的事物被吸引 左碰碰 右摸摸 陣陣風(fēng)兒隨地飄 ...
    文藝臉兒閱讀 106評(píng)論 9 5