Foundation類簇類的method swizzling

!D汲选5懊!7踩琛=渲啊!此帖子內容有待更深的考究M盖:樵铩!H槲凇E踉稀!:翰佟T倮础!

ios開發(fā)中磷瘤,在很多情形下芒篷,會用到 method swizzling 方式,即hook某一類(一般是不開源框架類)的某方法采缚,在方法的實現(xiàn)中添加自定義的邏輯针炉。

比如,對于NSMutableDictionary類的setValue:forKey:方法(此方法實現(xiàn)在 NSMutableDictionary針對kvc支持的拓展中)扳抽,添加key是否為空的判斷篡帕,以避免key為nil導致的崩潰等情況殖侵。
此時,需在NSMutableDictionary類的拓展類中添加重載load類方法的代碼镰烧,代碼套路如下:

+ (void)load {
        Class class = [self class];    //objc_getClass("__NSDictionaryM")  // line 1
        SEL selector_origin = @selector(setValue:forKey:);                         //line 2
        SEL selector_new = @selector(XXSetValue:forKey:);                      //line 3
        //XXSetValue:forKey: 方法為自定義方法拢军,略

        Method method_origin = class_getInstanceMethod(class, selector_origin);  // line 4
        Method method_new = class_getInstanceMethod(class, selector_new);        //line 5
        
        method_exchangeImplementations(method_origin, method_new);                  //line 6
}

此時,當在程序中其他地方怔鳖,包含了此部分代碼的頭文件時茉唉,正常調用setValue: forKey:時,實際上已經調用了自定義的方法 XXSetValue:forKey: 结执。以上代碼的作用就是將兩方法的實現(xiàn)代碼互換(method_exchangeImplementations方法)赌渣。

廢話說了這么多,重點在這昌犹,下圖為替換 setObject:forKey:方法的實現(xiàn)代碼:

        Class class = [self class];   //objc_getClass("__NSDictionaryM");         //line 0

        SEL selector_origin = @selector(setObject:forKey:);         //line 1
        SEL selector_new = @selector(XsetObj:forKey:);                //line 2
        
        Method method_origin = class_getInstanceMethod(class, selector_origin);   //line 3
        Method method_new = class_getInstanceMethod(class, selector_new);        //line 4
        
        method_exchangeImplementations(method_origin, method_new);            //line 5

注意坚芜,重點:在這里的hook實際上事很特殊的,因為如果此時swizzling的方法不是setValue:forKey:而是setObject:forKey:的話(代碼見以上)斜姥,結果會大不相同(實際結果是后者的method swizzling不會成功)鸿竖;而如果想使setObject:forKey:的swizzling成功的話,需要在代碼 line 0 處铸敏,換成注釋的部分缚忧。(--而 對setValue方法時,使用objc_getClass("__NSDictionaryM")或者[self class]都是成功的)

問題來了:
1杈笔、為什么都是類的實例方法的 setobject和setvalue闪水,實現(xiàn)method swizzling 的方式并不完全一樣呢?蒙具?
2球榆、 class_getInstanceMethod() 函數(shù)的第一參數(shù)的意義是什么?禁筏?

/由于作者水平限制持钉,以下解釋中,類簇的相關知識并不詳細且嚴謹篱昔。每强。/

首先,method_exchangeImplementations()函數(shù)的implementation交換是在類對象中完成州刽,即問題2 中的函數(shù)參數(shù)空执,所以問題2 的答案應該是 第一參數(shù)是方法所在的類對象,或父類的類對象(稍后會解釋)穗椅;當selector為類方法而不是實例方法時辨绊,應該使用class_getClassMethod()函數(shù),對應的房待,問題2 的參數(shù)應該是當前類的元類對象邢羔,或者根元類(NSObject)的元類對象。

ps: 拓展一個知識點桑孩, [self class]無論在類方法中還是實例方法中拜鹤,返回都是類對象。而object_getClass(self)函數(shù)流椒,當在類方法中時敏簿,返回的是類對象的isa指針指向的類,即元類對象宣虾;在實例方法中惯裕,返回的才是類對象。-------因此要避免使用不合適的類對象做參數(shù)

其次绣硝,為什么setobject和setvalue方法的method swizzling 實現(xiàn)不一樣呢蜻势?首先,需要明白類簇的原理鹉胖,(以下關于類簇的知識只是總結了一些碎塊化的知識點握玛,然后瞎逼逼的。)簡單理解的話甫菠,類簇即根據(jù)不同的初始化方法挠铲,選擇多套子類代碼中某個作為當前類,進行實例的初始化寂诱,如[[NSNumber alloc] initWithFloat: 1.2f]和[[NSNumber alloc] initWithDouble: 1.3]會產生不同的類拂苹,所以可以理解為不同初始化方式得到的類可能不是同一個類,不同用途的類也可能不是同一個類----這里的setvalue對應的類痰洒,即Method結構 method_origin的類對象參數(shù)瓢棒,和setobject對應的類不是同一個個類----雖然都屬于NSMutableDictionary類(繼承自公共基類"__NSDictionaryM")。

想要實現(xiàn)正確的method swizzling丘喻,需要自當前類對象音羞,沿著繼承關系,依次向上替換method(考慮繼承類中有方法的重載現(xiàn)象--例如自定義UIViewController類重寫viewDidLoad方法)仓犬;在不考慮繼承類重寫方法的時候嗅绰,使用類簇的根基類(即implementation了當前方法的&&最上層的類):

這里引用牛逼的人 的一個方法,獲取某類的所有直接子類列表:

//打印出 某類的所有直接子類
+ (NSArray *)findAllOf:(Class)defaultClass {
    int count = objc_getClassList(NULL, 0);
    if (count <= 0) {
        @throw@"Couldn't retrieve Obj-C class-list";
        return @[defaultClass];
    }
    NSMutableArray * output = @[].mutableCopy;
    Class * classes = (Class *) malloc(sizeof(Class) * count);
    objc_getClassList(classes, count);
    for (int i = 0; i < count; ++i) {
        if (defaultClass == class_getSuperclass(classes[i]))//子類
        {
            [output addObject: classes[i]];
        }   
    }
    free(classes);
    return output.copy;  
}

正確的方法替換如下:

while (class_getInstanceMethod(currentClass, @selector(setObject:forKey:))) {
            Class superClass = [currentClass superclass];
            IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(setObject:forKey:)));
            IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(setObject:forKey:)));
            if (classResumeIMP != superclassResumeIMP &&
                originalAFResumeIMP != classResumeIMP) {
                [self swizzleMethodForClass:currentClass];
// swizzleMethodForClass 方法是 完成方法交換
            }
            currentClass = [currentClass superclass];
        }

最終通過setObject和setValue的例子搀继,也可以得出牛逼的人 一篇帖子中的一個說法窘面,子類若未重載基類的方法,在class_getInstanceMethod調用后叽躯,會copy一份基類的方法到自己的方法列表中财边;而實際進行方法交換的method就是這一份拷貝。因此点骑,就會導致在類簇類進行method swizzling時酣难,class參數(shù)的選取不當谍夭,導致swizzling失敗。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末憨募,一起剝皮案震驚了整個濱河市紧索,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菜谣,老刑警劉巖珠漂,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尾膊,居然都是意外死亡媳危,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門冈敛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來待笑,“玉大人,你說我怎么就攤上這事抓谴∽叹酰” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵齐邦,是天一觀的道長椎侠。 經常有香客問我,道長措拇,這世上最難降的妖魔是什么我纪? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮丐吓,結果婚禮上浅悉,老公的妹妹穿的比我還像新娘。我一直安慰自己券犁,他們只是感情好术健,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粘衬,像睡著了一般荞估。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上稚新,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天勘伺,我揣著相機與錄音,去河邊找鬼褂删。 笑死飞醉,一個胖子當著我的面吹牛,可吹牛的內容都是我干的屯阀。 我是一名探鬼主播缅帘,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轴术,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钦无?” 一聲冷哼從身側響起逗栽,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铃诬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苍凛,經...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡趣席,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了醇蝴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宣肚。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖悠栓,靈堂內的尸體忽然破棺而出霉涨,到底是詐尸還是另有隱情,我是刑警寧澤惭适,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布笙瑟,位于F島的核電站,受9級特大地震影響癞志,放射性物質發(fā)生泄漏往枷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一凄杯、第九天 我趴在偏房一處隱蔽的房頂上張望错洁。 院中可真熱鬧,春花似錦戒突、人聲如沸屯碴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽导而。三九已至,卻和暖如春隔崎,著一層夾襖步出監(jiān)牢的瞬間嗡载,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工仍稀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留洼滚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓技潘,卻偏偏與公主長得像遥巴,于是被迫代替她去往敵國和親千康。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354