iOS底層原理:Method Swizzling原理和注意事項

Method Swizzling 是什么贝咙?

Method Swizzling的含義是方法交換,其核心內(nèi)容是使用runtime api在運行時將一個方法的實現(xiàn)替換成另一個方法的實現(xiàn)剑逃。我們利用它可以替換系統(tǒng)或者我們自定義類的方法實現(xiàn)虽界,進而達到我們的特殊目的漓穿,這就是我們常說的iOS黑魔法枷餐。

本文Demo地址:Github-JQMethodSwizzling

Method Swizzling 原理

OC方法在底層是通過方法編號SEL函數(shù)實現(xiàn)IMP一一對應(yīng)進行關(guān)聯(lián)的。打個比方撵颊,OC類好比一本書宇攻,SEL就像是書中的目錄,IMP相當(dāng)于每條目錄所對應(yīng)的頁碼倡勇。關(guān)系如圖所示:

1.png

方法交換的代碼如下:

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swiSEL);
    method_exchangeImplementations(oriMethod, swiMethod);

交換后的關(guān)系如圖所示:

2.png

從上述方法交換可以看出:

  • 交換的是兩者的方法實現(xiàn)逞刷,也就是說調(diào)用oriSEL方法時,最終走的方法實現(xiàn)是swiIMP妻熊;調(diào)用swiSEL方法時夸浅,最終走的方法實現(xiàn)是oriIMP
  • 由此可見扔役,在進行方法交換操作時帆喇,如果交換代碼調(diào)用了兩次或多次(2的倍數(shù)),就會導(dǎo)致方法實現(xiàn)又交換了回去亿胸,相當(dāng)于交換了個寂寞坯钦,所以交換代碼建議放在單例下進行來保證方法交換的有效性。

方法交換在使用中的遞歸調(diào)用分析

首先侈玄,我們來創(chuàng)建一個JQStudent類婉刀,類中有兩個實例方法,jq_studentInstanceMethodstudentInstanceMethod序仙;然后突颊,在load方法中對兩個方法進行交換;最后,jq_studentInstanceMethod的實現(xiàn)中再次調(diào)用jq_studentInstanceMethod方法律秃。

代碼實現(xiàn)如下圖:

3.png

我們看到爬橡,這里會在jq_studentInstanceMethod方法中再次調(diào)用該方法,會不會引起遞歸調(diào)用呢棒动?

運行結(jié)果如下圖:

4.png

從運行結(jié)果看糙申,并沒有引起遞歸。這是因為進行方法交換后迁客,在執(zhí)行[st studentInstanceMethod]時郭宝,實際上找到的是jq_studentInstanceMethod的方法實現(xiàn),而jq_studentInstanceMethod方法實現(xiàn)中又執(zhí)行[self jq_studentInstanceMethod]掷漱,同樣是因為方法交換粘室,此時jq_studentInstanceMethod的方法實現(xiàn)也已經(jīng)指向了studentInstanceMethod,所以并不會引起遞歸調(diào)用卜范。相反衔统,如果我們在jq_studentInstanceMethod方法中調(diào)用了[self studentInstanceMethod]才是會引起遞歸調(diào)用的,小伙伴們一定要注意:Q=蹙簟!
流程如下圖:

5.png

在實際的開發(fā)中奥裸,我們常采用這種方式對業(yè)務(wù)流程中的一些關(guān)鍵方法進行方法交換(俗稱hook)险掀,從而達到不影響業(yè)務(wù)流程的情況下完成一些信息的收集工作,而這種方式則被稱為AOPAspect Oriented Programming湾宙,面向切面編程)樟氢。AOP是一種編程的思想,區(qū)別于OOPObject Oriented Programming侠鳄,面向?qū)ο缶幊蹋┎嚎小F鋵?strong>OOPAOP都是一種編程的思想,只不過OOP編程思想更加傾向于對業(yè)務(wù)模塊的封裝伟恶,劃分出更加清晰的邏輯單元碴开。而AOP則是面向切面進行提取封裝,提取各個模塊中的公共部分博秫,提高模塊的復(fù)用率潦牛,降低業(yè)務(wù)之間的耦合性。

方法交換的坑點和分析

坑點一:交換父類的方法

我們還在剛才的Demo中來演示挡育,現(xiàn)在有一個JQStudent類了罢绽,再創(chuàng)建一個JQPerson類,讓JQStudent繼承JQPerson静盅,在JQPerson類添加一個實例方法personInstanceMethod,在JQStudent類的load方法中將jq_studentInstanceMethod方法和父類中的personInstanceMethod方法進行交換。

實現(xiàn)代碼如下圖:

6.png
7.png

運行結(jié)果如下圖:

8.png

從上面的結(jié)果可以看到:

  • 子類JQStudent對象調(diào)用父類JQPerson的方法personInstanceMethod蒿叠,消息發(fā)送會通過方法查找從而找到父類方法并調(diào)用明垢。
  • 但是此時父類JQPerson中的方法personInstanceMethod對應(yīng)的方法實現(xiàn)已經(jīng)被交換成了子類JQStudentjq_studentInstanceMethod,因此會執(zhí)行子類的jq_studentInstanceMethod方法實現(xiàn)市咽。
  • 同理痊银,此時子類中調(diào)用jq_studentInstanceMethod方法,會執(zhí)行父類的personInstanceMethod方法實現(xiàn)施绎。

這樣看起來好像沒有什么問題八莞铩!緊接著谷醉,我們再使用父類JQPerson對象調(diào)用一下personInstanceMethod方法致稀,如下圖:

9.png

啪、啪俱尼、啪抖单,報錯了!S霭恕矛绘!我們來分析下什么原因,

  • 首先刃永,父類調(diào)換用personInstanceMethod方法會執(zhí)行子類中的jq_studentInstanceMethod方法實現(xiàn)货矮。
  • 然后又調(diào)用了jq_studentInstanceMethod方法,但是斯够,此時的調(diào)用者是JQPerson對象囚玫,父類JQPerson中并沒有jq_studentInstanceMethod方法實現(xiàn)。所以因方法找不到而報錯雳刺。

出了問題劫灶,我們來解決以下,將交換方式換成下面這種:

10.png
11.png

再看運行結(jié)果:

12.png

此時掖桦,我們的運行不報錯了本昏,而且JQStudent對象調(diào)用父類的personInstanceMethod方法,確實走了方法交換后的流程枪汪,JQPerson對象也正常的調(diào)用了personInstanceMethod方法涌穆,互不影響。為什么呢雀久?
原因是:

  1. 在方法交換前宿稀,先嘗試給本類添加一下oriSEL方法,方法實現(xiàn)為swiMethod赖捌;
  2. 如果添加成功則返回YES祝沸,代表本類中原本沒有oriSEL的方法實現(xiàn)矮烹;接著,再將父類的方法實現(xiàn)oriMethod替換給本類的swiSEL罩锐;
  3. 添加失敗則返回NO奉狈,代表本類中已有oriSEL的方法實現(xiàn),進行正常的方法交換即可涩惑。

坑點二:交換的父類中并沒有實現(xiàn)的方法

如果要交換的父類方法并沒有實現(xiàn)呢仁期?直接看下運行結(jié)果:

13.png
14.png

什么情況?我的天竭恬,遞歸了u说啊!痊硕!為什么呢赊级?我們斷點調(diào)試一下,看圖解釋:

15.png
16.png

從上面這些坑中寿桨,我們可以得出一些結(jié)論:

  • 方法交換要遵循功能單一原則此衅,也就是說本類交換本類中的方法,不能影響父類亭螟,否則會影響父類和兄弟姐妹的行為(方法)挡鞍;
  • 即使要交換父類的方法,也要在本類中實現(xiàn)(重寫)父類的方法预烙;
  • 本類或父類交換的方法實現(xiàn)不存在墨微,要給本類添加這個方法實現(xiàn),否則會出現(xiàn)遞歸調(diào)用

基于以上特點扁掸,我封裝一個更好的方法交換方式翘县,請看以下代碼實現(xiàn):

17.png

運行結(jié)果如下:

18.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谴分,隨后出現(xiàn)的幾起案子锈麸,更是在濱河造成了極大的恐慌,老刑警劉巖牺蹄,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忘伞,死亡現(xiàn)場離奇詭異,居然都是意外死亡沙兰,警方通過查閱死者的電腦和手機氓奈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鼎天,“玉大人舀奶,你說我怎么就攤上這事≌洌” “怎么了育勺?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵但荤,是天一觀的道長。 經(jīng)常有香客問我涧至,道長纱兑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任化借,我火速辦了婚禮,結(jié)果婚禮上捡多,老公的妹妹穿的比我還像新娘蓖康。我一直安慰自己,他們只是感情好垒手,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布蒜焊。 她就那樣靜靜地躺著,像睡著了一般科贬。 火紅的嫁衣襯著肌膚如雪泳梆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天榜掌,我揣著相機與錄音优妙,去河邊找鬼。 笑死憎账,一個胖子當(dāng)著我的面吹牛套硼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胞皱,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邪意,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了反砌?” 一聲冷哼從身側(cè)響起雾鬼,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宴树,沒想到半個月后策菜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡森渐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年做入,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片同衣。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡竟块,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耐齐,到底是詐尸還是另有隱情浪秘,我是刑警寧澤蒋情,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站耸携,受9級特大地震影響棵癣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夺衍,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一狈谊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沟沙,春花似錦河劝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颊咬,卻和暖如春务甥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喳篇。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工敞临, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杭隙。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓哟绊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親痰憎。 傳聞我的和親對象是個殘疾皇子票髓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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