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)系如圖所示:
方法交換的代碼如下:
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swiSEL);
method_exchangeImplementations(oriMethod, swiMethod);
交換后的關(guān)系如圖所示:
從上述方法交換可以看出:
- 交換的是兩者的方法實現(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_studentInstanceMethod
和studentInstanceMethod
序仙;然后突颊,在load
方法中對兩個方法進行交換;最后,jq_studentInstanceMethod
的實現(xiàn)中再次調(diào)用jq_studentInstanceMethod
方法律秃。
代碼實現(xiàn)如下圖:
我們看到爬橡,這里會在jq_studentInstanceMethod
方法中再次調(diào)用該方法,會不會引起遞歸調(diào)用呢棒动?
運行結(jié)果如下圖:
從運行結(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=蹙簟!
流程如下圖:
在實際的開發(fā)中奥裸,我們常采用這種方式對業(yè)務(wù)流程中的一些關(guān)鍵方法進行方法交換(俗稱hook
)险掀,從而達到不影響業(yè)務(wù)流程的情況下完成一些信息的收集工作,而這種方式則被稱為AOP
(Aspect Oriented Programming
湾宙,面向切面編程)樟氢。AOP
是一種編程的思想,區(qū)別于OOP
(Object Oriented Programming
侠鳄,面向?qū)ο缶幊蹋┎嚎小F鋵?strong>OOP
和AOP
都是一種編程的思想,只不過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)代碼如下圖:
運行結(jié)果如下圖:
從上面的結(jié)果可以看到:
- 子類
JQStudent
對象調(diào)用父類JQPerson
的方法personInstanceMethod
蒿叠,消息發(fā)送會通過方法查找從而找到父類方法并調(diào)用明垢。 - 但是此時父類
JQPerson
中的方法personInstanceMethod
對應(yīng)的方法實現(xiàn)已經(jīng)被交換成了子類JQStudent
的jq_studentInstanceMethod
,因此會執(zhí)行子類的jq_studentInstanceMethod
方法實現(xiàn)市咽。 - 同理痊银,此時子類中調(diào)用
jq_studentInstanceMethod
方法,會執(zhí)行父類的personInstanceMethod
方法實現(xiàn)施绎。
這樣看起來好像沒有什么問題八莞铩!緊接著谷醉,我們再使用父類JQPerson
對象調(diào)用一下personInstanceMethod
方法致稀,如下圖:
啪、啪俱尼、啪抖单,報錯了!S霭恕矛绘!我們來分析下什么原因,
- 首先刃永,父類調(diào)換用
personInstanceMethod
方法會執(zhí)行子類中的jq_studentInstanceMethod
方法實現(xiàn)货矮。 - 然后又調(diào)用了
jq_studentInstanceMethod
方法,但是斯够,此時的調(diào)用者是JQPerson
對象囚玫,父類JQPerson
中并沒有jq_studentInstanceMethod
方法實現(xiàn)。所以因方法找不到而報錯雳刺。
出了問題劫灶,我們來解決以下,將交換方式換成下面這種:
再看運行結(jié)果:
此時掖桦,我們的運行不報錯了本昏,而且JQStudent
對象調(diào)用父類的personInstanceMethod
方法,確實走了方法交換后的流程枪汪,JQPerson
對象也正常的調(diào)用了personInstanceMethod
方法涌穆,互不影響。為什么呢雀久?
原因是:
- 在方法交換前宿稀,先嘗試給本類添加一下
oriSEL
方法,方法實現(xiàn)為swiMethod
赖捌; - 如果添加成功則返回
YES
祝沸,代表本類中原本沒有oriSEL
的方法實現(xiàn)矮烹;接著,再將父類的方法實現(xiàn)oriMethod
替換給本類的swiSEL
罩锐; - 添加失敗則返回
NO
奉狈,代表本類中已有oriSEL
的方法實現(xiàn),進行正常的方法交換即可涩惑。
坑點二:交換的父類中并沒有實現(xiàn)的方法
如果要交換的父類方法并沒有實現(xiàn)呢仁期?直接看下運行結(jié)果:
什么情況?我的天竭恬,遞歸了u说啊!痊硕!為什么呢赊级?我們斷點調(diào)試一下,看圖解釋:
從上面這些坑中寿桨,我們可以得出一些結(jié)論:
- 方法交換要遵循功能單一原則此衅,也就是說本類交換本類中的方法,不能影響父類亭螟,否則會影響父類和兄弟姐妹的行為(方法)挡鞍;
- 即使要交換父類的方法,也要在本類中實現(xiàn)(重寫)父類的方法预烙;
- 本類或父類交換的方法實現(xiàn)不存在墨微,要給本類添加這個方法實現(xiàn),否則會出現(xiàn)遞歸調(diào)用
基于以上特點扁掸,我封裝一個更好的方法交換方式翘县,請看以下代碼實現(xiàn):
運行結(jié)果如下: