合理使用oc的runtime特性堆巧,可以為我們的應(yīng)用開發(fā)和解決業(yè)務(wù)需求提供極大的便利礁叔。這篇文章權(quán)當(dāng)學(xué)習(xí)AOP源碼的學(xué)習(xí)筆記分享出來牍颈。
aop 編程值aspect第三方庫的使用解析
.h 文件聲明
AspectOptions
AspectOptions聲明了切面編程對原方法的處理方式。默認(rèn)為AspectPositionAfter琅关,在原始方法之后調(diào)用煮岁。AspectPositionInstead 替代原始方法
AspectPositionBefore 在原方法之前調(diào)用
AspectOptionAutomaticRemoval,在第一次hook之后死姚,會自動移除hook
定義的兩個協(xié)議
AspectToken:允許注銷hook人乓,協(xié)議中只有一個remove方法,返回yes則注銷hook成功都毒,返回no則注銷失敗
AspectInfo:是aop切面block的第一個參數(shù)色罚。主要包含了hook的信息。主要由instance账劲,originalInvocation戳护,arguments組成。分別是當(dāng)前hook的實例瀑焦,hook方法的原始invocation以及所有方法的參數(shù)腌且。
切面編程的兩個方法
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
第一個方法是為類添加一個block,第二個方法是為實例添加一個block榛瓮。兩個方法都無法為靜態(tài)方法添加hook铺董。
幾個錯誤碼
AspectErrorCode:表明返回的錯誤類型;
AspectErrorSelectorBlacklisted, /// Selectors like release, retain, autorelease are blacklisted.
AspectErrorDoesNotRespondToSelector, /// Selector could not be found.
AspectErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed.
AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed.
AspectErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair.
AspectErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called.
AspectErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large.
AspectErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated.
.m文件方法解析
_AspectBlock:結(jié)構(gòu)體
AspectIdentifier:追蹤單個的aspect
包含有追蹤的selector禀晓、block精续、blockSignautre方法簽名、options切面選項粹懒、
AspectsContainer:一個對象或者類的所有切面
包含有三個數(shù)組分別用于存放hook前重付,hook替代的方法、及hook后方法
AspectTracker:所hook類的相關(guān)信息
包含類名凫乖、所有的方法名
aspects hook調(diào)用棧
以實例對象的調(diào)用為例:
-
(id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error{}方法為共有API确垫,調(diào)用的是
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error)弓颈。
第一個參數(shù)傳本對象、第二個參數(shù)傳當(dāng)前hook的selector删掀、第三個傳block翔冀、第四個hook選項。
aspect_add方法主要將參數(shù)傳入做下一步的處理爬迟。
處理流程:
斷言確保傳入的參數(shù)不為空橘蜜;創(chuàng)建一個AspectIdentifier對象用于保存hook的信息。
static void aspect_performLocked(dispatch_block_t block) {
static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&aspect_lock);
block();
OSSpinLockUnlock(&aspect_lock);
}
aspect_performLocked加了線程安全鎖付呕,用于保證線程安全的情況下執(zhí)行block计福。保證當(dāng)前線程只有一個在hook。
在aspect_performLocked中徽职,先是創(chuàng)建AspectsContainer對象象颖,接著將傳入的參數(shù)組裝成AspectIdentifier對象。如果對象不為空姆钉,則AspectsContainer添加該對象说订,最后執(zhí)行aspect_prepareClassAndHookSelector()方法進(jìn)行hook。
aspect_isSelectorAllowedAndTrack()
該方法判斷當(dāng)前函數(shù)能否被hook潮瓶,不能被hook的函數(shù)有retain陶冷、release、autorelease毯辅、forwardInvocation,以及delloc之后調(diào)用埂伦,并將track過的方法加入到tracker中。
先創(chuàng)建不能被hook的名單思恐,接著檢查傳入的方法是否是處于名單中的方法若是則返回No.如果hook的是delloc方法沾谜,則檢查是在delloc前還是delloc后,delloc后調(diào)用是錯誤的胀莹。
繼續(xù)判斷當(dāng)前hook的是不是類對象基跑,如果是類對象則先判斷是否在子類中hook過,如果hook過子類相同的方法則返回NO;沒有hook過則在當(dāng)前類的父類中方法查找判斷描焰。
首先拿到以當(dāng)前類為key媳否,從字典中拿到tracker,接著判斷tracker中是否含有要hook的方法如果有再判斷是否是當(dāng)前類荆秦,是則返回yes逆日,no則之前已經(jīng)hook過。
該方法用于記錄hook的信息萄凤。創(chuàng)建一個tracker對象,將hook過的方法加入到tracker字典中搪哪,這個字典是一個全局的單例靡努。
aspect_prepareClassAndHookSelector();
為切面編程的核心方法。
1、獲取當(dāng)前要hook的類
2惑朦、拿到目標(biāo)方法的Method
3兽泄、拿到目標(biāo)方法的實現(xiàn)imp地址
4、判斷目標(biāo)imp是否是消息轉(zhuǎn)發(fā)的方法漾月,若不是進(jìn)行相應(yīng)的替換
1病梢、拿到目標(biāo)方法的typeEncoding
2、為selector添加自定義前綴
3梁肿、判斷當(dāng)前類實例是否響應(yīng)添加前綴的selector如果不響應(yīng)則新增方法
4蜓陌、替換方法的實現(xiàn)為自身的方法。
aspect_aliasForSelector()
該方法主要是為selector添加上自定義的前綴 @"aspects_"
aspect_getMsgForwardIMP()
獲取消息轉(zhuǎn)發(fā)的函數(shù)實現(xiàn)吩蔑,用于替換原有的selector的方法實現(xiàn)钮热;
_objc_msgForward
是一個函數(shù)指針,與IMP類似用于消息轉(zhuǎn)發(fā)烛芬。當(dāng)一個對象發(fā)送消息并沒有實現(xiàn)的時候隧期,_objc_msgForward
會嘗試做消息轉(zhuǎn)發(fā)。
該方法主要的目的是在于返回的是_objc_msgForward
赘娄,還是_objc_msgForward_stret
仆潮。先判斷非arm64的情況下,接著判斷返回值的大小來決定是否使用_objc_msgForward_stret
遣臼。關(guān)于返回值的詳細(xì)說明可以參考官方文檔性置。關(guān)于為什么要在非arm64位的情況下判斷可以參考這篇文章。
清除hook 方法
與添加hook相對應(yīng)的就是清除hook方法aspect_remove暑诸;
aspect_remove
可以發(fā)現(xiàn)首先是一個鎖aspect_performLocked蚌讼,防止多線程同時修改。具體步奏如下:
1个榕、獲取AspectsContainer對象
2篡石、根據(jù)aspect,AspectIdentifier尋找對應(yīng)的實例刪除
3西采、調(diào)用aspect_cleanupHookedClassAndSelector撤銷runtime時做的改變
4凰萨、將aspect對象重置
aspect_cleanupHookedClassAndSelector()
該方法是整個撤銷過程的核心,主要是將runtime時hook的imp復(fù)原械馆,且將tracker等置空胖眷。
綜述
以上是自己最近研究學(xué)習(xí)AOP源碼的一些認(rèn)識,受限于個人水平,如有不正確的地方,還請批評指正软吐。后續(xù)會繼續(xù)更新本篇學(xué)習(xí)筆記粘招。