AOP 源碼學(xué)習(xí)筆記

合理使用oc的runtime特性,可以為我們的應(yīng)用開發(fā)和解決業(yè)務(wù)需求提供極大的便利。這篇文章權(quán)當(dāng)學(xué)習(xí)AOP源碼的學(xué)習(xí)筆記分享出來(lái)。

aop 編程值aspect第三方庫(kù)的使用解析

.h 文件聲明

AspectOptions

AspectOptions聲明了切面編程對(duì)原方法的處理方式。默認(rèn)為AspectPositionAfter,在原始方法之后調(diào)用。AspectPositionInstead 替代原始方法
AspectPositionBefore 在原方法之前調(diào)用
AspectOptionAutomaticRemoval,在第一次hook之后,會(huì)自動(dòng)移除hook

定義的兩個(gè)協(xié)議

AspectToken:允許注銷hook,協(xié)議中只有一個(gè)remove方法,返回yes則注銷hook成功,返回no則注銷失敗

AspectInfo:是aop切面block的第一個(gè)參數(shù)。主要包含了hook的信息。主要由instance,originalInvocation,arguments組成。分別是當(dāng)前hook的實(shí)例,hook方法的原始invocation以及所有方法的參數(shù)。

切面編程的兩個(gè)方法

  • (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;

第一個(gè)方法是為類添加一個(gè)block,第二個(gè)方法是為實(shí)例添加一個(gè)block。兩個(gè)方法都無(wú)法為靜態(tài)方法添加hook。

幾個(gè)錯(cuò)誤碼

AspectErrorCode:表明返回的錯(cuò)誤類型;

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:追蹤單個(gè)的aspect

包含有追蹤的selector、block、blockSignautre方法簽名、options切面選項(xiàng)、

AspectsContainer:一個(gè)對(duì)象或者類的所有切面

包含有三個(gè)數(shù)組分別用于存放hook前,hook替代的方法、及hook后方法

AspectTracker:所hook類的相關(guān)信息

包含類名、所有的方法名

aspects hook調(diào)用棧

以實(shí)例對(duì)象的調(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)。
    第一個(gè)參數(shù)傳本對(duì)象、第二個(gè)參數(shù)傳當(dāng)前hook的selector、第三個(gè)傳block、第四個(gè)hook選項(xiàng)。
    aspect_add方法主要將參數(shù)傳入做下一步的處理。


    W

處理流程:
斷言確保傳入的參數(shù)不為空;創(chuàng)建一個(gè)AspectIdentifier對(duì)象用于保存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)前線程只有一個(gè)在hook。


在aspect_performLocked中,先是創(chuàng)建AspectsContainer對(duì)象,接著將傳入的參數(shù)組裝成AspectIdentifier對(duì)象。如果對(duì)象不為空,則AspectsContainer添加該對(duì)象,最后執(zhí)行aspect_prepareClassAndHookSelector()方法進(jìn)行hook。

aspect_isSelectorAllowedAndTrack()

該方法判斷當(dāng)前函數(shù)能否被hook,不能被hook的函數(shù)有retain、release、autorelease、forwardInvocation,以及delloc之后調(diào)用,并將track過(guò)的方法加入到tracker中。


先創(chuàng)建不能被hook的名單,接著檢查傳入的方法是否是處于名單中的方法若是則返回No.如果hook的是delloc方法,則檢查是在delloc前還是delloc后,delloc后調(diào)用是錯(cuò)誤的。


繼續(xù)判斷當(dāng)前hook的是不是類對(duì)象,如果是類對(duì)象則先判斷是否在子類中hook過(guò),如果hook過(guò)子類相同的方法則返回NO;沒(méi)有hook過(guò)則在當(dāng)前類的父類中方法查找判斷。
首先拿到以當(dāng)前類為key,從字典中拿到tracker,接著判斷tracker中是否含有要hook的方法如果有再判斷是否是當(dāng)前類,是則返回yes,no則之前已經(jīng)hook過(guò)。

WX20190410-135709@2x.png

該方法用于記錄hook的信息。創(chuàng)建一個(gè)tracker對(duì)象,將hook過(guò)的方法加入到tracker字典中,這個(gè)字典是一個(gè)全局的單例。

aspect_prepareClassAndHookSelector();

為切面編程的核心方法。


WX20190410-145805@2x.png

1、獲取當(dāng)前要hook的類
2、拿到目標(biāo)方法的Method
3、拿到目標(biāo)方法的實(shí)現(xiàn)imp地址
4、判斷目標(biāo)imp是否是消息轉(zhuǎn)發(fā)的方法,若不是進(jìn)行相應(yīng)的替換
1、拿到目標(biāo)方法的typeEncoding
2、為selector添加自定義前綴
3、判斷當(dāng)前類實(shí)例是否響應(yīng)添加前綴的selector如果不響應(yīng)則新增方法
4、替換方法的實(shí)現(xiàn)為自身的方法。

aspect_aliasForSelector()

該方法主要是為selector添加上自定義的前綴 @"aspects_"

aspect_getMsgForwardIMP()

獲取消息轉(zhuǎn)發(fā)的函數(shù)實(shí)現(xiàn),用于替換原有的selector的方法實(shí)現(xiàn);


WX20190429-094114@2x.png

_objc_msgForward是一個(gè)函數(shù)指針,與IMP類似用于消息轉(zhuǎn)發(fā)。當(dāng)一個(gè)對(duì)象發(fā)送消息并沒(méi)有實(shí)現(xiàn)的時(shí)候,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)。
該方法主要的目的是在于返回的是_objc_msgForward,還是_objc_msgForward_stret。先判斷非arm64的情況下,接著判斷返回值的大小來(lái)決定是否使用_objc_msgForward_stret。關(guān)于返回值的詳細(xì)說(shuō)明可以參考官方文檔。關(guān)于為什么要在非arm64位的情況下判斷可以參考這篇文章。

清除hook 方法

與添加hook相對(duì)應(yīng)的就是清除hook方法aspect_remove;

aspect_remove

可以發(fā)現(xiàn)首先是一個(gè)鎖aspect_performLocked,防止多線程同時(shí)修改。具體步奏如下:
1、獲取AspectsContainer對(duì)象
2、根據(jù)aspect,AspectIdentifier尋找對(duì)應(yīng)的實(shí)例刪除
3、調(diào)用aspect_cleanupHookedClassAndSelector撤銷runtime時(shí)做的改變
4、將aspect對(duì)象重置

aspect_cleanupHookedClassAndSelector()

該方法是整個(gè)撤銷過(guò)程的核心,主要是將runtime時(shí)hook的imp復(fù)原,且將tracker等置空。

綜述

以上是自己最近研究學(xué)習(xí)AOP源碼的一些認(rèn)識(shí),受限于個(gè)人水平,如有不正確的地方,還請(qǐng)批評(píng)指正。后續(xù)會(huì)繼續(xù)更新本篇學(xué)習(xí)筆記。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容