一步一步實(shí)現(xiàn)自定義KVO

聽起來(lái)KVO很強(qiáng)大院水,其實(shí)自己實(shí)現(xiàn)也是可以的泵琳。但是想做到像系統(tǒng)實(shí)現(xiàn)的那樣還是比較費(fèi)事的佃扼,這次我們就搞一個(gè)簡(jiǎn)單一點(diǎn)的正卧,但是功能和系統(tǒng)提供的幾乎一致迅皇。

首先說(shuō)一下原理榴徐,其實(shí)就是在調(diào)用方法

addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid*)context

的時(shí)候技掏,系統(tǒng)在這個(gè)方法內(nèi)部通過(guò)runtime創(chuàng)建了需要監(jiān)聽對(duì)象的類的子類署恍,然后將isa指針指向了這個(gè)子類隔显,同時(shí)又添加了需要監(jiān)聽屬性的set方法却妨、dealloc、_isKOVA括眠、重寫了class方法彪标。

因?yàn)楸槐O(jiān)聽對(duì)象的isa指針指向了動(dòng)態(tài)創(chuàng)建的子類,所以當(dāng)修改屬性的時(shí)候就調(diào)用了子類的set方法掷豺,然后在set方法內(nèi)部做一些處理捞烟,比如獲取舊值,設(shè)置新值当船,調(diào)用?willChangeValueForKey题画、didChangeValueForKey,向observeValueForKeyPath:ofObject:change:context:發(fā)送消息等等一些操作德频。

重寫class方法主要是為了讓開發(fā)者調(diào)用[xx class]的時(shí)候返回的還是原來(lái)的類苍息,但是如果使用object_getClass(xx)這個(gè)時(shí)候獲取到的就是真正的類了。

當(dāng)然想看一下動(dòng)態(tài)創(chuàng)建子類的所有方法可以使用runtime的class_copyMethodList方法獲取壹置。

下面開始自己實(shí)現(xiàn)KVO竞思。

先來(lái)觀察兩張圖片

通過(guò)這兩張圖就可以看出來(lái)蘋果也是在添加觀察者的時(shí)候自動(dòng)創(chuàng)建了一個(gè)被觀察對(duì)象的子類,知道了原理就好辦了

其實(shí)核心就是使用runtime,好了廢話不多說(shuō)直接開擼.

1,首先創(chuàng)建一個(gè)NSObject分類, ?在.h里邊搞一個(gè)供外部添加觀察者的方法

@interfaceNSObject (YHKVO)

- (void)YH_addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid*)context;

@end


2, ? ?在.m實(shí)現(xiàn)該方法

首先通過(guò)原類創(chuàng)建一個(gè)原類的子類,創(chuàng)建好了以后不要忘記注冊(cè),系統(tǒng)創(chuàng)建的子類前綴是NSKVONotifying_,我們也搞一個(gè)和系統(tǒng)差不多的YHKVONotifying_

????NSString *oldName = NSStringFromClass(self.class);

? ? NSString *newName = [@"YHKVONotifying_" stringByAppendingString:oldName];

? ? ClassMyClass =objc_getClass(newName.UTF8String);

? ? if(!MyClass) {

? ? ? ? MyClass =objc_allocateClassPair(self.class, newName.UTF8String, 0);

? ? ? ? //注冊(cè)類

? ? ? ? objc_registerClassPair(MyClass);

? ? }


3,需要在子類中添加一個(gè)方法,獲取添加方法需要的所有參數(shù)

????//set方法首字母大寫

? ? NSString *keyPathChange = [[[keyPath substringToIndex:1] uppercaseString] stringByAppendingString:[keyPath substringFromIndex:1]];

? ? NSString*setNameStr = [NSStringstringWithFormat:@"set%@", keyPathChange];

? ? SEL setSEL = NSSelectorFromString([setNameStr stringByAppendingString:@":"]);

? ? //添加子類的set方法

? ? Method getMethod = class_getInstanceMethod([self class], @selector(keyPath));

? ? constchar*types =method_getTypeEncoding(getMethod);

? ? /**

?? ? *class 給哪個(gè)類添加方法

?? ? *sel 方法編號(hào)

?? ? *imp 方法實(shí)現(xiàn)(函數(shù)指針)

?? ? *type返回值類型以及參數(shù)

?? ? */

? ? class_addMethod(MyClass, setSEL, (IMP)setMethod, types);


4,修改當(dāng)前指針指向子類,修改了以后在添加觀察者的時(shí)候打斷點(diǎn)就可以看到指針的指向改變了

????//修改isa指針????

? ? object_setClass(self, MyClass);


5,保存接下來(lái)需要使用的方法

????//保存方法

? ? objc_setAssociatedObject(self, YH_observer, observer, OBJC_ASSOCIATION_ASSIGN);

? ? objc_setAssociatedObject(self, YH_settter, setNameStr, OBJC_ASSOCIATION_COPY_NONATOMIC);

? ? objc_setAssociatedObject(self, YH_getter, keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);

6,實(shí)現(xiàn)void setMethod方法

所有的objc_msgSend()最少需要兩個(gè)參數(shù),第一個(gè)參數(shù)self, 第二個(gè)_cmd

????void setMethod(id self, SEL _cmd, id newVlaue) {

? ? NSString *setName = objc_getAssociatedObject(self, YH_settter);

? ? NSString *getName = objc_getAssociatedObject(self, YH_getter);

? ? Classclass = [selfclass];//獲取當(dāng)前類型


? ? object_setClass(self, class_getSuperclass(class));//修改isa指針到父類


? ? //調(diào)用原類get, 獲取舊值

? ? id oldValue = objc_msgSend(self, NSSelectorFromString(getName));

? ? //調(diào)用原類,設(shè)置新值

? ? objc_msgSend(self, NSSelectorFromString([setName stringByAppendingString:@":"]), newVlaue);


? ? id observer = objc_getAssociatedObject(self, YH_observer);


? ? NSMutableDictionary * change = [NSMutableDictionary dictionary];

? ? if(newVlaue) {

? ? ? ? change[NSKeyValueChangeNewKey] = newVlaue;

? ? }

? ? if(oldValue) {

? ? ? ? change[NSKeyValueChangeOldKey] = oldValue;

? ? }


? ? if(observer) {

? ? ? ? ????????objc_msgSend(observer,@selector(observeValueForKeyPath:ofObject:change:context:), getName,self, change,nil);

? ? }

? ? //修改回子類,供下次使用

? ? object_setClass(self, class);

}

測(cè)試:不出意外一切正常

- (void)viewDidLoad {

? ? [super viewDidLoad];

? ? _p= [[Persionalloc]init];

? ? [_p YH_addObserver:self forKeyPath:NSStringFromSelector(@selector(age)) options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];

}

- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context {

? ? NSLog(@"%@",_p.age);

? ? NSLog(@"%@", change);

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {

? ? staticinta =0;

? ? a++;

? ? _p.age = [NSString stringWithFormat:@"%d", a];

}


如有不足請(qǐng)指正

源碼地址:?GitHub - yeshibuzhong/iOS

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钞护,隨后出現(xiàn)的幾起案子盖喷,更是在濱河造成了極大的恐慌,老刑警劉巖患亿,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件传蹈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡步藕,警方通過(guò)查閱死者的電腦和手機(jī)惦界,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咙冗,“玉大人沾歪,你說(shuō)我怎么就攤上這事∥硐” “怎么了灾搏?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)立润。 經(jīng)常有香客問(wèn)我狂窑,道長(zhǎng),這世上最難降的妖魔是什么桑腮? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任泉哈,我火速辦了婚禮,結(jié)果婚禮上破讨,老公的妹妹穿的比我還像新娘丛晦。我一直安慰自己,他們只是感情好提陶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布烫沙。 她就那樣靜靜地躺著,像睡著了一般隙笆。 火紅的嫁衣襯著肌膚如雪锌蓄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天仲器,我揣著相機(jī)與錄音煤率,去河邊找鬼。 笑死乏冀,一個(gè)胖子當(dāng)著我的面吹牛蝶糯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辆沦,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昼捍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了肢扯?” 一聲冷哼從身側(cè)響起妒茬,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔚晨,沒(méi)想到半個(gè)月后乍钻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肛循,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年银择,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了多糠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浩考,死狀恐怖夹孔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情析孽,我是刑警寧澤搭伤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站袜瞬,受9級(jí)特大地震影響怜俐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜邓尤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一佑菩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裁赠,春花似錦殿漠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至一忱,卻和暖如春莲蜘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帘营。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工票渠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芬迄。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓问顷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親禀梳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杜窄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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