Aspects 實現原理

Aspects 是 Objective-C 比較知名的 AOP 框架,實現方法調劑(method swizzling)。經過使用 Aspects 提供的接口,比直接使用 runtime 提供的接口,更加方便靈活。ios

Aspects 如今不建議在生產環境使用,但它的實現原理,仍是很是值得學習和借鑑的。git

Aspects 支持實例和類的方法的調劑,雖然內部調用的是同一個方法,在實現上有較大的差異。github

實例的方法交換

- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error {
    return aspect_add(self, selector, options, block, error);
}
複製代碼

- aspect_hookSelector內部調用的aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error)由 C 函數實現。該函數有五個參數:數組

  • self:當前調劑方法的對象;
  • selector:被調劑的方法名;
  • options:調劑的方式類型爲 AspectOptions;
  • block:調劑selector方法是須要混入執行的 block;
  • error: 調劑過程發生的錯誤信息;

AspectOptions 有四種類型,用於決定 block 和原方法執行的順序,分別有在原方法以前、以後、或者直接替換。安全

咱們逐一看看 aspect_add 函數的內部實現:框架

__block AspectIdentifier *identifier = nil;
// 1
aspect_performLocked(^{
	// 2
    if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
        // 3
        AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
        // 4
        identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
        if (identifier) {
            // 5
            [aspectContainer addAspect:identifier withOptions:options];
            // 6
            aspect_prepareClassAndHookSelector(self, selector, error);
        }
    }
});

複製代碼

一、 爲方法調劑加鎖,保證線程安全,使用的是自旋鎖,然而 OSSpinLockLock 已再也不安全ide

二、 判斷當前 selector 是否支持調劑。若是被調劑方法是 retain, release, autorelease, forwardInvocation:則返回錯誤。forwardInvocation方法不支持調劑是由於 Aspects 的實現基於這個方法,後面會講到。至於其餘幾個內存管理的方法,筆者不是很肯定不能調劑的緣由,多是在這些方法中對象處於不穩定狀態, 訪問當前對象存在安全隱患。函數

接着是保證調劑dealloc方法只能是AspectPositionBefore類型,dealloc 負責釋放資源,不能被替換,執行後對象釋放不能再執行 block 操做。學習

最後判斷當前對象可否響應被調劑的方法。ui

三、 aspectContainer以關聯對象的形式做爲self的屬性,屬性的名稱爲 selector加前綴aspects_

@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
複製代碼

AspectsContainer類包含三個數組屬性,分別用於保存在 block 三種執行的狀況。

四、 AspectIdentifier以對象的形式封裝調劑信息,其中包含方法名,執行的對象,執行時機,以及執行的 block。

五、 將 aspect identifier 存儲到當前對象關聯的 container 中。在方法被調用的時候,會遍歷這個 container 中全部的 identifier,根據它包含的信息來執行。

六、 這是最關鍵的一步,也是 Aspects 實現的核心所在。主要分爲兩部分,

1)處理被 hook 方法的對象所屬的類

Class aspect_hookClass(NSObject *self, NSError **error)
複製代碼

若是對象所屬的類已經被 Aspects 處理過就直接返回,不然該函數爲當前實例對象所屬的類,動態的建立一個子類,並 hook 該子類的forwardInvocation方法,指向本身的__ASPECTS_ARE_BEING_CALLED__函數實現。

重置該子類對象和元類的 class 方法,使其返回(父類)被 hook 方法所在的類名。最後將當前實例selfisa指針指向這個子類,做用是當self接受消息時,會先在這個子類方法列表查找。

此時若是self收到的消息消息沒法處理,會走forwardInvocation對應的實現,而前面這個方法已經被 hook 指向咱們本身的實現,在咱們本身實現中,經過NSInvocation能取出當前消息的接受對象,經過該對象從關聯屬性中取出 container 來執行。

如今的目標是若是經過調用被 hook 的方法,來觸發forwardInvocation的調用。

2)處理被 hook 的實現 在 <objc/message.h> 頭文件中有一個函數_objc_msgForward它的函數指針就是與 forwardInvocation關聯的imp

只要咱們將 hook 的方法與 _objc_msgForward實現交換,就能觸發forwardInvocationimp了。

在調劑原selectorimp指向_objc_msgForward函數以前,須要先判斷該 selector 是否已經被調劑過,若是已是_objc_msgForward的實現就不做處理。

若是不是,咱們要對selector的實現作交換了,假如咱們直接將其與 _objc_msgForward交換,若是對象的另外一個selector也作 hook,就會把前一個selector的實現給覆蓋了。因此這裏動態的添加一個 aliasSelector方法用於保存原selector的實現,再用_objc_msgForward函數替換原 selector 的實現。

當被調劑的方法執行時,會執行動態建立的子類的forwardInvocation方法,而在1)中咱們講到該方法的實現已經被 hook,指向了 __ASPECTS_ARE_BEING_CALLED__函數。

__ASPECTS_ARE_BEING_CALLED__函數的實現其實並不複雜,它從 forwardInvocation的參數NSInvacation 中取出觸發消息轉發的 selector,將它替換爲aspects_前綴的別名selector,由於這個別名 selector指向前面保存的原selector的實現。

接着從self中取出使用以別名selector做爲屬性的關聯對象 AspectsContainer,container 中包含selector方法被調劑的信息集合。先執行 before 的 block,接着執行封裝在invocation中的原實現,最後執行 after 類型的 block。若是有 instead 類型的 block 則原實現不會被執行。

其中 block 並非直接執行 aspect 中保存的那個,而是對其參數進行了處理,在第一個參數中插入 AspectInfo 類型的實例。

類的方法交換

類的方法交換流程與實例的方法交換大體相同,主要區別在於對同一繼承鏈上的類 hook 相同的方法進行了限制。

實例和類調用的都是aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error)函數,因此 self參數多是實例,也多是類,源碼中使用 object_getClass,若是是實例對象會返回類,若是是類會返回元類,再經過class_isMetaClass判斷self是不是爲類。

aspect_isSelectorAllowedAndTrack函數中,對類的狀況作了一大堆處理,其實就作一件事情:判斷當前self所在的繼承鏈是否有同名的 selector已經被 hook 過了。

若是沒有,就記錄當前self的繼承關係,直到基類NSObject

之因此不能 hook 具備繼承鏈關係同名方法,是由於若是子類方法調用super會致使死循環。

關鍵緣由在於super關鍵字的實現,它是編譯器的一個助記符,在方法調用時,會在父類的方法列表中查找,但無論在哪一層級找到,消息的接受者仍然是當前類/實例。也就是說在執行super方法時,若是父類的該方法也被調用,就會走__ASPECTS_ARE_BEING_CALLED__函數,該函數中的 NSInvocation 參數中的 target依然是self,這就致使[invocation invoke]調用時,觸發的仍是當前selfselector。也就致使了循環調用。

- (void)helloInstanceMethod {
    [super helloInstanceMethod]; // 致使 [self helloInstanceMethod]
}
複製代碼

對比總結

Aspects 在類層面上進行方法的調劑時,直接「原地」調劑forwardInvocation指向本身的實現,並將要 hook 的方法指向消息轉發的實現。同時它必須保證同一條繼承鏈上,不能 hook 同一個方法。

然而,對於實例對象的方法卻不須要,由於實例方法的 hook 會動態的建立子類,並修改消息轉發的實現,保證原類的其餘實例方法不會受影響。

相關文章
相關標籤/搜索