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

合理使用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ù)傳入做下一步的處理。


    W

處理流程:
斷言確保傳入的參數(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過。

WX20190410-135709@2x.png

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

aspect_prepareClassAndHookSelector();

為切面編程的核心方法。


WX20190410-145805@2x.png

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);


WX20190429-094114@2x.png

_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í)筆記。

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

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

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