KVO實現(xiàn)原理 自定義KVO

原理

注冊一個中間類KVO_xxx繼承自要觀察的類胆筒,通過isa-swizzling將xxx類的isa指像新的的類KVO_XXX. 并且動態(tài)給新類重寫setter方法眶根,以達到屬性攔截目的,從而觸發(fā)回調(diào)通知觀察者屬性是否改變道批。


class的底層結(jié)構(gòu)圖.png

isa指針,顧名思義, 指向的對象的類維護調(diào)度表欣硼。這個調(diào)度表基本上包含指向類實現(xiàn)?方法,以 及其他數(shù)據(jù)。 將這個isa指向子類KVO_XXX從而在用戶無感知的狀態(tài)下實現(xiàn)setter方法重寫拙泽。

實現(xiàn)步驟

--先看步驟,最后有demo裸燎。

1.自定義一個category 當調(diào)用xlAddObserver的時候開始生成中間類顾瞻,當有監(jiān)聽的屬性改變的時候觸發(fā)回調(diào)
@interface NSObject (XLKVO)

//監(jiān)聽
-(void)xlAddObserver:(id)obj forKeypath:(NSString *)keyPath;

//回調(diào)
-(void)xlObserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object;

@end
2.根據(jù)要監(jiān)聽的類生成一個KVO_XXX的中間類
-(void)xlAddObserver:(id)obj forKeypath:(NSString *)keyPath {
    //1.注冊一個中間類XLKVO_開頭的子類 繼承自當前類
    Class superClass = object_getClass(self);
    NSString * newClassName = [NSString stringWithFormat:@"XLKVO_%@",NSStringFromClass(superClass)];
    //判斷下是否注冊過
    Class newClass;
    if (!NSClassFromString(newClassName)) {
        //創(chuàng)建新類
        newClass = [self registerNewClass:superClass newClassName:newClassName];
        //改變 當前類的isa只向newClass 此時所有的函數(shù)執(zhí)行都要經(jīng)過子類newClass了
        object_setClass(self, newClass);
    }
}

//注冊一個新的類
-(Class)registerNewClass:(Class)superClass newClassName:(NSString *)newClassName{
    //生成一個類繼承自superClass
    Class newClass = objc_allocateClassPair(superClass, newClassName.UTF8String, 0);
    //注冊
    objc_registerClassPair(newClass);
    return newClass;
}
2.關(guān)聯(lián)對象,關(guān)聯(lián)observer和keypath以便重寫setter的時候調(diào)用觸發(fā)
//關(guān)聯(lián)對象 obj顺少,以便在后面通知執(zhí)行回調(diào)方法
    objc_setAssociatedObject(self, @"xlobserver", obj, OBJC_ASSOCIATION_RETAIN);
    objc_setAssociatedObject(self, @"keyPath", keyPath, OBJC_ASSOCIATION_RETAIN);
3. 給KVO_XXX類動態(tài)追加上寫要監(jiān)聽的屬性的setter方法 。 這里的self已經(jīng)經(jīng)過了object_setClass(self, newClass);函數(shù)指向了新的KVO類了
   //動態(tài)給newClass添加一個setter方法 實現(xiàn)方法重寫完成監(jiān)聽回調(diào)
    //注:這里的所有的self都是指的新的類
    NSString * setterName = [self setterNameFromKeyPath:keyPath];
    Method setterMethod = class_getInstanceMethod(superClass, NSSelectorFromString(setterName));
    const char * types = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, NSSelectorFromString(setterName), (IMP)xlKVOStter, types);

根據(jù)屬性名字拼接setter方法

//拼接setter方法名
-(NSString *)setterNameFromKeyPath:(NSString *)name {
    NSString * firstName = [[name substringToIndex:1]uppercaseString];
    NSString * otherStr = [name substringFromIndex:1];
    return [NSString stringWithFormat:@"set%@%@:",firstName,otherStr];
}

指定setter函數(shù)的imp實現(xiàn),在調(diào)用類的setter函數(shù)的時候脆炎,其實是在完成一個消息發(fā)送梅猿,objc_msgSend,最終指向行真正的函數(shù)實現(xiàn),在這個地方我們就完成了屬性的攔截秒裕,從而達到監(jiān)聽的目的袱蚓。

//setter方法的IMP實現(xiàn) 前兩個參數(shù)固定的寫法 _cmd在Objective-C的方法中表示當前方法的selector
static void xlKVOStter(id self,SEL _cmd,id value) {
    //1.執(zhí)行父類的方法
    //消息轉(zhuǎn)發(fā) 直接去執(zhí)行父類的
    //結(jié)構(gòu)體 點的作用是結(jié)構(gòu)體成員指定初始化
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    //取結(jié)構(gòu)體地址 執(zhí)行消息發(fā)送
    objc_msgSendSuper(&superStruct,_cmd,value);
    
    
    //2.調(diào)用observer通知屬性即將改變
    id obj = objc_getAssociatedObject(self, @"xlobserver");
    NSString * keyPath = objc_getAssociatedObject(self, @"keyPath");
    [obj xlObserveValueForKeyPath:keyPath ofObject:self];
   
}

細節(jié)優(yōu)化

在此我們就已經(jīng)完成了KVO的基本功能了,不過還有一些小細節(jié)需要優(yōu)化一下几蜻。

1.在給KVO類添加setter的時候先判斷一下喇潘,以免重復(fù)添加.

我們可以通過獲取該類的method列表來判斷是否已經(jīng)追加了

//判斷是否存在該方法
-(BOOL)hasSelector:(SEL)selector {
    Class myclass = object_getClass(self);
    unsigned int methodCount = 0;
    //獲取該類的方法列表,列表是一個結(jié)構(gòu)體指針的數(shù)組
    Method * methodList = class_copyMethodList(myclass, &methodCount);
    
    for (int i = 0; i < methodCount; i++) {
        SEL sel = method_getName(methodList[i]);
        if (sel == selector) {
            free(methodList);
            return YES;
        }
    }
    free(methodList);
    return NO;
}
2.給KVO添加一個class方法,返回父類的class梭稚,以達到無感知的目的
//重寫對象的class方法,將KVO_XXX的class指向監(jiān)聽類的class颖低,達到用戶無感知的目的
    Method classMethod = class_getInstanceMethod(superClass, NSSelectorFromString(@"class"));
    const char * classTypes = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, NSSelectorFromString(@"class"), (IMP)KVOClass, classTypes);
static Class KVOClass (id self,SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}

到此整個功能已經(jīng)完成了。
需要demo可以加QQ群:839813029

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弧烤,一起剝皮案震驚了整個濱河市忱屑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暇昂,老刑警劉巖莺戒,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異急波,居然都是意外死亡从铲,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門澄暮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來名段,“玉大人,你說我怎么就攤上這事赏寇〖郏” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵嗅定,是天一觀的道長自娩。 經(jīng)常有香客問我,道長渠退,這世上最難降的妖魔是什么忙迁? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮碎乃,結(jié)果婚禮上姊扔,老公的妹妹穿的比我還像新娘。我一直安慰自己梅誓,他們只是感情好恰梢,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布佛南。 她就那樣靜靜地躺著,像睡著了一般嵌言。 火紅的嫁衣襯著肌膚如雪嗅回。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天摧茴,我揣著相機與錄音绵载,去河邊找鬼。 笑死苛白,一個胖子當著我的面吹牛娃豹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播购裙,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼懂版,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缓窜?” 一聲冷哼從身側(cè)響起定续,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎禾锤,沒想到半個月后私股,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡恩掷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年倡鲸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黄娘。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡峭状,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逼争,到底是詐尸還是另有隱情优床,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布誓焦,位于F島的核電站胆敞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏杂伟。R本人自食惡果不足惜移层,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赫粥。 院中可真熱鬧观话,春花似錦、人聲如沸越平。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晦溪,卻和暖如春龟再,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尼变。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浆劲,地道東北人嫌术。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像牌借,于是被迫代替她去往敵國和親度气。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354