iOS AOP框架Aspects實現原理

Aspects主要是利用了forwardInvocation進行轉發,Aspects其實利用和kvo類似的原理,通過動態創建子類的方式,把對應的對象isa指針指向創建的子類,然后把子類的forwardInvocation的IMP替成ASPECTS_ARE_BEING_CALLED,假設要hook的方法名XX,在子類中添加一個Aspects_XX的方法,然后將Aspects_XX的IMP指向原來的XX方法的IMP,這樣方便后面調用原始的方法,再把要hook的方法XX的IMP指向_objc_msgForward,這樣就進入了消息轉發流程,而forwardInvocation的IMP被替換成了ASPECTS_ARE_BEING_CALLED,這樣就會進入ASPECTS_ARE_BEING_CALLED進行攔截處理,這樣整個流程大概結束。

// 動態生成子類 把子類的方法替換成自己的
aspect_hookClass

        // Default case. Create dynamic subclass. 
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    Class subclass = objc_getClass(subclassName);

    if (subclass == nil) {
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);//(subclassName子類名_加后綴_Aspects_)
        if (subclass == nil) {
            NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }

  // 把子類的 forwardInvocation 指向 __ASPECTS_ARE_BEING_CALLED__
 // 替換子類的 forwardInvocation 方法
aspect_swizzleForwardInvocation(subclass);

======

    // 由于新創建的class沒有實現 forwardInvocation  因此 這里的 originalImplementation就是空的
    // 使用 __ASPECTS_ARE_BEING_CALLED__ 替換子類的 forwardInvocation 方法實現
    // 由于子類本身并沒有實現 forwardInvocation ,
    // 所以返回的 originalImplementation 將為空值,所以子類也不會生成 AspectsForwardInvocationSelectorName 這個方法

static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    if (originalImplementation) {
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

// 修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回當前對象的 class 隱藏對外的Class 類似KVO
aspect_hookedGetClass(subclass, statedClass); subclass 動態生成的子類
aspect_hookedGetClass(object_getClass(subclass), statedClass);

static void aspect_hookedGetClass(Class class, Class statedClass) {
    NSCParameterAssert(class);
    NSCParameterAssert(statedClass);
    Method method = class_getInstanceMethod(class, @selector(class));
    IMP newIMP = imp_implementationWithBlock(^(id self) {
        return statedClass;
    });
    class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
}

第一部分主要是動態生成一個子類,然后把子類的forwardInvocation函數動態替換成ASPECTES_ARE_BEING_CALLED,然后通過object_setClass把self的isa指針指向subClass,這樣后期調用的時候,會直接進入到動態創建的這個子類中來

    // 被 hook 的 selector
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);

    // 判斷需要被Hook的方法是否應指向 _objc_msgForward 進入消息轉發模式
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, it not already copied.
        // 讓一個新的子類方法名指向原先方法的實現,處理回調
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        SEL aliasSelector = aspect_aliasForSelector(selector);
        if (![klass instancesRespondToSelector:aliasSelector]) {
            __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
            NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
        }

        // We use forwardInvocation to hook in.
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }

第二部分aspect_isMsgForwardIMP是會提前進行判斷原方法是否有被Hook走,有的話就不會再操作(這里和JSPatch會沖突),然后有兩部操作,第一步把動態生成的帶有前綴的方法的IMP指向原方法的實現,第二步把原來函數的IMP指針指向objc_msgForward直接進入消息轉發流程

最終Hook到以下函數,執行完之后回調到原來的方法實現,到此所有流程就結束了

// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    SEL originalSelector = invocation.selector;
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    invocation.selector = aliasSelector;
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;

    // Before hooks.
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);

    // Instead hooks.
    BOOL respondsToAlias = YES;
    if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
        aspect_invoke(classContainer.insteadAspects, info);
        aspect_invoke(objectContainer.insteadAspects, info);
    }else {
        Class klass = object_getClass(invocation.target);
        do {
            if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
                [invocation invoke];
                break;
            }
        }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
    }

    // After hooks.
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);

    // If no hooks are installed, call original implementation (usually to throw an exception)
    if (!respondsToAlias) {
        invocation.selector = originalSelector;
        SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            [self doesNotRecognizeSelector:invocation.selector];
        }
    }

    // Remove any hooks that are queued for deregistration.
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

該函數為swizzle后, 實現新IMP統一處理的核心方法 , 完成一下幾件事

處理調用邏輯, 有before, instead, after, remove四種option
將block轉換成一個NSInvocation對象以供調用
從AspectsContainer根據aliasSelector取出對象, 并組裝一個AspectInfo, 帶有原函數的調用參數和各項屬性, 傳給外部的調用者 (在這是block) .
調用完成后銷毀帶有removeOption的hook邏輯, 將原selector掛鉤到原IMP上, 刪除別名selector

1.找到被 hook 的 originalSelector 的 方法實現

2.新建一個 aliasSelector 指向原來的 originalSelector 的方法實現

3.動態創建一個 originalSelector 所在實例的子類,然后 hook 子類的 forwardInvocation: 方法并將方法的實現替換成 ASPECTS_ARE_BEING_CALLED 方法

4.originalSelector 指向 _objc_msgForward 方法實現

5.實例的 originalSelector 的方法執行的時候,實際上是指向 objc_msgForward ,而 objc_msgForward 的方法實現被替換成ASPECTS_ARE_BEING_CALLED 的方法實現,也就是說 originalSelector 的方法執行之后,實際上執行的是__ASPECTS_ARE_BEING_CALLED 的方法實現。而 aliasSelector 的作用就是用來保存 originalSelector 的方法實現,當 hook 代碼執行完成之后,可以回到 originalSelector 的原始方法實現上繼續執行

1.找到被 hook 的 originalSelector 的 方法實現

2.新建一個 aliasSelector 指向原來的 originalSelector 的方法實現

3.動態創建一個 originalSelector 所在實例的子類,然后 hook 子類的 forwardInvocation: 方法并將方法的實現替換成 ASPECTS_ARE_BEING_CALLED 方法

4.originalSelector 指向 _objc_msgForward 方法實現

5.實例的 originalSelector 的方法執行的時候,實際上是指向 objc_msgForward ,而 objc_msgForward 的方法實現被替換成ASPECTS_ARE_BEING_CALLED 的方法實現,也就是說 originalSelector 的方法執行之后,實際上執行的是__ASPECTS_ARE_BEING_CALLED 的方法實現。而 aliasSelector 的作用就是用來保存 originalSelector 的方法實現,當 hook 代碼執行完成之后,可以回到 originalSelector 的原始方法實現上繼續執行


作者:Deft_MKJing宓珂璟
來源:CSDN
原文:https://blog.csdn.net/Deft_MKJing/article/details/79567663
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容