MethodSwizzling

背景

? ?有一天贡这,項(xiàng)目里要替換整個(gè)項(xiàng)目里一個(gè)方法的所有的實(shí)現(xiàn),老大說(shuō):“如果是你你怎么辦厂榛?這就像個(gè)面試題”盖矫,我在那里回答"繼承"丽惭、methodSignature、refactor辈双、一堆答案沒(méi)有一個(gè)命中责掏。老大給了我一個(gè)methodSwizzing的連接,于是就有了這個(gè)文章

問(wèn)題

一個(gè)常見(jiàn)的場(chǎng)景就是湃望,當(dāng)我們想對(duì)大量同樣的方法執(zhí)行某個(gè)相同的操作换衬。例如我們想在每個(gè)ViewController的viewDidLoad方法中添加某行代碼,當(dāng)然我們可以通過(guò)繼承证芭、讓UIViewController瞳浦、UINavigationController等都實(shí)現(xiàn)這個(gè)方法。這樣废士,它們的子類也就會(huì)執(zhí)行這個(gè)方法叫潦。但是,這樣會(huì)導(dǎo)致我們需要在各種ViewController中添加代碼官硝,而且如果有的子類并沒(méi)有調(diào)用父類的viewDidLoad矗蕊,那就覆蓋不到這個(gè)子類。此外泛源,在視圖控制器的生命周期拔妥,響應(yīng)事件,繪制視圖等場(chǎng)景中达箍,都會(huì)由這個(gè)需求没龙,這個(gè)時(shí)候method swizzling 就能夠?yàn)殚_(kāi)發(fā)帶來(lái)很好的作用。

示例代碼

?最常見(jiàn)的hook代碼

+ (void)swizzMethod:(SEL)origSel altMethod:(SEL)altSel {

????????Method?origMethod?=class_getInstanceMethod(self,origSel);

????????Method altMethod = class_getInstanceMethod(self, altSel);

????????BOOL didAddMethod =?class_addMethod([self class],origSel, ????????method_getImplementation(altMethod),method_getTypeEncoding(altMethod));

????????if (didAddMethod) {

????????????class_replaceMethod([self class], ????????????altSel,method_getImplementation(origMet),method_getTypeEncoding(origMet))

????????????} else {

????????????method_exchangeImplementations(class_getInstanceMethod(self, origSel_), ????????????class_getInstanceMethod(self, altSel_));

????????? }

}

代碼解釋

我看到這個(gè)代碼的時(shí)候缎玫,除了認(rèn)得字母外硬纤,一無(wú)所知??所以我首先查了下里面的每行代碼做了什么。

首先代碼中使用到了class_getInstanceMethod(self, origSel)這個(gè)方法用來(lái)返回一個(gè)class中指定的實(shí)例方法赃磨,如果class或者它的superclass不存在對(duì)應(yīng)的實(shí)例方法筝家,就會(huì)返回NULL

然后代碼中使用了class_addMethod這個(gè)方法用來(lái)給一個(gè)class添加一個(gè)指定名稱和實(shí)現(xiàn)的方法: BOOL?class_addMethod(Class cls, SEL name, IMP imp, const char *types);

a)???方法一共有四個(gè)參數(shù):class是要添加方法的class,name是方法的name邻辉,imp是方法的實(shí)現(xiàn),實(shí)現(xiàn)里至少需要兩個(gè)參數(shù)self和_cmd溪王。?types是描述參數(shù)類型的字符串構(gòu)成的一個(gè)數(shù)組

b)???方法會(huì)重寫superclass's?實(shí)現(xiàn),但是不會(huì)替換這個(gè)類里已有的實(shí)現(xiàn)

c)???一個(gè)Objective-C?方法就是一個(gè)至少有兩個(gè)參數(shù)的c方法。例如對(duì)于一個(gè)給定的方法:

????????i.?void myMethodIMP(id self, SEL _cmd){ // implementation ....}

d)???我們可以像下面的方法一樣動(dòng)態(tài)的將它以resolveThisMethodDynamicall名字添加到class中

????????i.?class_addMethod([self class], @selector(resolveThisMethodDynamically), (IMP) myMethodIMP, "v@:");

知道了以上兩個(gè)基礎(chǔ)值骇,就可以看到莹菱。實(shí)際上methodSwizzling是先添加了名稱是被替換方法的一個(gè)指定的替換后的方法

然后方法調(diào)用了一個(gè)class_replaceMethod函數(shù),函數(shù)共四個(gè)參數(shù),第一個(gè)參數(shù)是要修改的方法所在的類吱瘩,第二個(gè)參數(shù)是要替換實(shí)現(xiàn)的函數(shù)名稱道伟,之后分別傳入方法實(shí)現(xiàn)和方法參數(shù)的字符串?dāng)?shù)組。同時(shí)方法會(huì)返回被替換函數(shù)的之前實(shí)現(xiàn)。

a)???如果函數(shù)名稱指定的方法之前不存在蜜徽,函數(shù)就會(huì)像method_add一樣為類添加一個(gè)方法祝懂。

b)???如果函數(shù)名稱指定的方法存在,函數(shù)就像method_setImplementation替換函數(shù)的實(shí)現(xiàn)

?知道了這點(diǎn)就可以看到拘鞋,方法替換函數(shù)第二步在成功添加了被替換函數(shù)同名的替換方法后砚蓬,會(huì)將替換函數(shù)名對(duì)應(yīng)的函數(shù)實(shí)現(xiàn)替換為之前的函數(shù)實(shí)現(xiàn)。如果添加函數(shù)不成功盆色,也就是類之前就存在一個(gè)同名的函數(shù)實(shí)現(xiàn)時(shí)會(huì)調(diào)用method_exchangeImplementations函數(shù)怜械,函數(shù)接收兩個(gè)?Method?類型參數(shù)。然后交換它們的實(shí)現(xiàn)傅事。所以缕允,methodSwizzling實(shí)際上相當(dāng)于將添加后的函數(shù)實(shí)現(xiàn)和原先的函數(shù)實(shí)現(xiàn),也就是IMP指針進(jìn)行了替換蹭越。

上述的代碼是寫在類本身的障本,當(dāng)然我們也可以在category中對(duì)某個(gè)類進(jìn)行方法替換,那么需要在load方法中調(diào)用上面swizzMethod:(SEL)origSel altMethod:(SEL)altSel相似的函數(shù)响鹃。只不過(guò)其中的實(shí)現(xiàn)需要改成先調(diào)用兩次class_addMethod將被替換函數(shù)和替換函數(shù)都加入到類中驾霜,然后,調(diào)用method_exchangeImplementations方法將兩個(gè)函數(shù)的實(shí)現(xiàn)進(jìn)行替換买置。

注意事項(xiàng)

首先看看load?和?initialize方法:swizzling?應(yīng)該只在?+load?中完成粪糙。

a)???在?Objective-C?的運(yùn)行時(shí)中,每個(gè)類有兩個(gè)方法都會(huì)自動(dòng)調(diào)用忿项。+load?是在一個(gè)類被初始裝載時(shí)調(diào)用蓉冈,也就是當(dāng)代碼還沒(méi)有被load的時(shí)候代碼從文件夾的代碼加載到運(yùn)行的程序中時(shí)候會(huì)調(diào)用load方法

b)???+initialize?是在應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的。兩個(gè)方法都是可選的轩触,并且只有在方法被實(shí)現(xiàn)的情況下才會(huì)被調(diào)用寞酿。

只調(diào)用一次

swizzling方法會(huì)替換所有同名的方法的實(shí)現(xiàn),所以脱柱,應(yīng)該確保這個(gè)方法只執(zhí)行一次伐弹,不會(huì)發(fā)生多次替換的情況,這個(gè)時(shí)候就可以用dispatch_once榨为,這個(gè)可以滿足所有的需求惨好,而且?dispatch_once也是初始化一個(gè)單例方法的標(biāo)準(zhǔn)方法,但是這種方式進(jìn)行的更改僅限于method所在的類,比如在UIView的子類B中進(jìn)行方法替換随闺,那么方法替換將僅限于B及B的子類日川。

Selector、SEL和IMP的概念

Selector?是一個(gè)在運(yùn)行時(shí)被注冊(cè)(或映射)的C類型字符串板壮。Selector由編譯器產(chǎn)生并且在當(dāng)類被加載進(jìn)內(nèi)存時(shí)由運(yùn)行時(shí)自動(dòng)進(jìn)行名字和實(shí)現(xiàn)的映射逗鸣。Method是用來(lái)表示函數(shù)定義的類型的一個(gè)結(jié)構(gòu)體合住,IMP是一個(gè)函數(shù)指針绰精,該方法的第一個(gè)參數(shù)指向調(diào)用方法的自身(即內(nèi)存中類的實(shí)例對(duì)象撒璧,若是調(diào)用類方法,該指針則是指向元類對(duì)象?metaclass)笨使。第二個(gè)參數(shù)是這個(gè)方法的名字?selector卿樱,該方法的真正參數(shù)緊隨其后。

防止遞歸

使用的時(shí)候硫椰,在交換方法實(shí)現(xiàn)后記得要調(diào)用原生方法的實(shí)現(xiàn)繁调,除非確定可以不用調(diào)用原生實(shí)現(xiàn),如果不調(diào)用靶草,可能會(huì)導(dǎo)致一些底層實(shí)現(xiàn)錯(cuò)誤例如下面這段代碼蹄胰。

a)???- (void) altSel {

???[self altSel];

}

b)???看起來(lái)這里會(huì)發(fā)生遞歸調(diào)用,但是 實(shí)際上因?yàn)橥鈱拥腶ltSel已經(jīng)替換成了origSel所以實(shí)際上是origSel在調(diào)用altSel?但是如果里邊換成[self origSel]?就會(huì)發(fā)生遞歸調(diào)用

防止父類和子類同時(shí)替換

應(yīng)該在替換的方法前增加前綴奕翔,如果不在load方法中添加替換裕寨,而是在caterory中執(zhí)行,因?yàn)閏ategory中的方法是在Runtime加載的時(shí)候加到類的MethodList,這就會(huì)導(dǎo)致如果altSel重名之后派继,SEL和IMP不匹配宾袜,導(dǎo)致hook的結(jié)果不對(duì)

被hook的方法應(yīng)該是類自身的方法,如果把繼承的IMP copy到自身上面會(huì)存在問(wèn)題驾窟,父類的方法應(yīng)該在調(diào)用的時(shí)候使用庆猫,不應(yīng)該swizzling的時(shí)候copy到子類,如果父類也hock了方法绅络。子類也hook了方法月培,父類hook的方法可能由于compileSourc中子類category順序在父類category之前,而導(dǎo)致只調(diào)用子類的hook方法,或者兩者都不調(diào)用恩急,舉個(gè)形象的例子节视, UIView有一個(gè)方法,實(shí)現(xiàn)叫做A假栓, UIView將方法的實(shí)現(xiàn)替換成了B寻行,這個(gè)時(shí)候,UIView的子類又去替換這個(gè)A原先方法名的實(shí)現(xiàn)匾荆,那么它替換的將是B拌蜘。也就是UIView的子類就沒(méi)有A的實(shí)現(xiàn)了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牙丽,一起剝皮案震驚了整個(gè)濱河市简卧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烤芦,老刑警劉巖举娩,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡铜涉,警方通過(guò)查閱死者的電腦和手機(jī)智玻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芙代,“玉大人吊奢,你說(shuō)我怎么就攤上這事∥婆耄” “怎么了页滚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)铺呵。 經(jīng)常有香客問(wèn)我裹驰,道長(zhǎng),這世上最難降的妖魔是什么片挂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任邦马,我火速辦了婚禮,結(jié)果婚禮上宴卖,老公的妹妹穿的比我還像新娘滋将。我一直安慰自己,他們只是感情好症昏,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布随闽。 她就那樣靜靜地躺著,像睡著了一般肝谭。 火紅的嫁衣襯著肌膚如雪掘宪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天攘烛,我揣著相機(jī)與錄音魏滚,去河邊找鬼。 笑死坟漱,一個(gè)胖子當(dāng)著我的面吹牛鼠次,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芋齿,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腥寇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了觅捆?” 一聲冷哼從身側(cè)響起赦役,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栅炒,沒(méi)想到半個(gè)月后掂摔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體术羔,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年乙漓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了级历。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡簇秒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秀鞭,到底是詐尸還是另有隱情趋观,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布锋边,位于F島的核電站皱坛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏豆巨。R本人自食惡果不足惜剩辟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望往扔。 院中可真熱鬧贩猎,春花似錦、人聲如沸萍膛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蝗罗。三九已至艇棕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間串塑,已是汗流浹背沼琉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桩匪,地道東北人打瘪。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像傻昙,于是被迫代替她去往敵國(guó)和親瑟慈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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

  • 原文地址 為什么有這篇博文 不知道何時(shí)開(kāi)始iOS面試開(kāi)始流行起來(lái)詢問(wèn)什么是 Runtime屋匕,于是 iOSer 一聽(tīng)...
    南梔傾寒閱讀 15,256評(píng)論 19 87
  • 前言 2016年6月7號(hào)開(kāi)始load/initalize/KVO/KVC/Block葛碧,并通過(guò)代碼實(shí)現(xiàn) load/i...
    js丶閱讀 308評(píng)論 0 1
  • MethodSwizzling的實(shí)現(xiàn) 首先oc中的方法,是一個(gè) objc_method 結(jié)構(gòu)體,由三部分組成 IM...
    forping閱讀 225評(píng)論 0 0
  • coding 的演示功能不讓用,原來(lái)搭建的博客訪問(wèn)不了了过吻。索性將全部博客遷移到簡(jiǎn)書(shū)进泼,這篇是舊文章蔗衡,歡迎大家以后來(lái)簡(jiǎn)...
    小笨狼閱讀 636評(píng)論 0 0
  • 原文地址 為什么有這篇博文 不知道何時(shí)開(kāi)始 iOS 面試開(kāi)始流行起來(lái)詢問(wèn)什么是 Runtime,于是 iOSer ...
    Just丶Go閱讀 4,410評(píng)論 0 34