Aspects原始碼分析

大神Q發表於2019-04-22

Aspects一直在專案中使用,主要使用AOP方式進行tracking,但還沒好好看一看,最近研究了一下原始碼,十分推薦大家閱讀一下,如果只是一味的看Runtime原始碼,很難真正的掌握還容易忘,配合著看像Aspects這樣優秀的框架有助於形成知識體系,而且程式碼量也不大,本文只是分析主要的一些原始碼,可以到我的github上看更多原始碼註釋AspectsAnalysis

NSMethodSignature和NSInvocation

大家可以試試以下程式碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 獲取某個類的例項方法簽名有兩種
    NSMethodSignature *signature = [self methodSignatureForSelector:@selector(test:)];
//    NSMethodSignature *signature = [ViewController instanceMethodSignatureForSelector:@selector(test:)];

    // 獲取某個類的類方法只有一種
//    NSMethodSignature *signature = [ViewController methodSignatureForSelector:@selector(test:)];

    // 獲取方法簽名對應的invocation
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

    // 設定訊息接受者,與[invocation setArgument:(__bridge void * _Nonnull)(self) atIndex:0]等價
    [invocation setTarget:self];

    // 設定要執行的selector, 與[invocation setArgument:@selector(test:) atIndex:1]
    [invocation setSelector:@selector(test:)];

    //設定引數
    NSString *str = @"hello world";
    [invocation setArgument:&str atIndex:2];

    //開始執行
    [invocation invoke];
}

- (void)test:(NSString*)string{
    NSLog(@"test  %@",string);
}


複製程式碼

結果是,通過NSMethodSignatureNSInvocation也能完成例項物件的方法呼叫

2019-04-20 16:00:57.027828+0800 Test[7439:627386] test  hello world


複製程式碼

NSMethodSignature

一個NSMethodSignature物件記錄著某個方法的返回值型別資訊以及引數型別資訊。它用於轉發訊息接收者無法響應的訊息。

上面程式碼中提供了獲得類方法和例項方法簽名的方式,也可以使用signatureWithObjCTypes建立方法簽名。

@interface NSMethodSignature
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
@end
複製程式碼

NSMethodSignature物件是根據字串建立的,這裡的字串代表了某個方法的返回值型別以及引數型別的字串編碼(Objective-C type encodings)。

個方法簽名包含代表方法返回值的一個或多個字元,後面跟上隱式引數self以及_cmd的字串編碼,然後後面再跟上零個或多個明確的引數的字元編碼。可以通過methodReturnType屬性獲取返回值型別的字元編碼,可以通過methodReturnLength屬性獲取返回值型別的長度。

例如:NSString的例項方法containsString:的方法簽名包含以下引數:

  1. 返回值:BOOL型別, @encode(BOOL) ——c
  2. 方法接收者(self):id型別,@encode(id)——@
  3. 方法名(_cmd):SEL,@encode(SEL)——:
  4. 方法引數:NSString型別,@encode(NSString *)——@

NSInvocation

NSInvocation封裝了方法呼叫物件、方法選擇器、引數、返回值等,可以給物件傳送一個引數大於兩個的訊息,可以直接設定這些元素中的每一個,並在NSInvocation排程物件時自動設定返回值。

Objective-C方法呼叫過程

Objective-C的方法呼叫過程中,如果selector有對應的IMP,則直接執行。如果沒有,在丟擲異常之前還有一些彌補機會,依次有resolveInstanceMethodforwardingTargetForSelectorforwardInvocatio

  • resolveInstanceMethod (或resolveClassMethod):實現該方法,可以通過class_addMethod新增方法,返回YES的話系統在執行時就會重新啟動一次訊息傳送的過程,NO的話會繼續執行下一個方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(runTo:)) {
        class_addMethod(self, sel, (IMP)dynamicMethodIMPRunTo, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
//動態新增的@selector(runTo:) 對應的實現
static void dynamicMethodIMPRunTo(id self, SEL _cmd,id place){
    NSLog(@"dynamicMethodIMPRunTo %@",place);
}


複製程式碼
  • forwardingTargetForSelector:實現該方法可以將訊息轉發給其他物件,只要這個方法返回的不是nilself,也會重啟訊息傳送的過程,把這訊息轉發給其他物件來處理。
-(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(dynamicSelector) && [self.myObj respondsToSelector:@selector(dynamicSelector)]) {
        return self.myObj;
    }else{
        return [super forwardingTargetForSelector:aSelector];
    }
}

複製程式碼

如果上面兩步都無法完成這個SEL的處理,就會通過forwardInvocation進行訊息轉發

  • methodSignatureForSelector:會去獲取一個方法簽名,如果沒有獲取到的話就回直接挑用doesNotRecognizeSelector,如果能獲取的話系統就會建立一個NSlnvocation傳給forwardInvocation方法。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{
    //判斷selector是否為需要轉發的,如果是則手動生成方法簽名並返回。
    if (aSelector == @selector(dynamicSelector)){
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super forwardingTargetForSelector:aSelector];
}

複製程式碼
  • forwardInvocation:該方法是上一個步傳進來的NSlnvocation,然後呼叫NSlnvocationinvokeWithTarget方法,轉發到對應的Target
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //判斷待處理的anInvocation是否為我們要處理的
    if (anInvocation.selector == @selector(dynamicSelector)){
    		
    }else{
    }
}


複製程式碼
  • doesNotRecognizeSelector:丟擲unrecognized selector sent to …異常

上面描述的是正常的方法呼叫過程,如果想手動出發訊息轉發怎麼辦呢?_objc_msgForward或者_objc_msgForward_stret,那他們區別是什麼呢?


IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
    if (typeDescription[0] == '{') {
        //In some cases that returns struct, we should use the '_stret' API:
        //http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
        //NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription.
        NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:typeDescription];
        if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) {
            msgForwardIMP = (IMP)_objc_msgForward_stret;
        }
    }

複製程式碼

JSPatch通過判斷方法簽名的 debugDescription 是不是包含特定字串is special struct return? YES,進而決定是否使用 _objc_msgForward_stret

JSPatch作者解釋

大多數CPU在執行C函式時會把前幾個引數放進暫存器裡,對 obj_msgSend 來說前兩個引數固定是 self / _cmd,它們會放在暫存器上,在最後執行完後返回值也會儲存在暫存器上,取這個暫存器的值就是返回值。普通的返回值(int/pointer)很小,放在暫存器上沒問題,但有些 struct 是很大的,暫存器放不下,所以要用另一種方式,在一開始申請一段記憶體,把指標儲存在暫存器上,返回值往這個指標指向的記憶體寫資料,所以暫存器要騰出一個位置放這個指標,self / _cmd 在暫存器的位置就變了。objc_msgSend 不知道 self / _cmd 的位置變了,所以要用另一個方法 objc_msgSend_stret 代替。原理大概就是這樣。在 NSMethodSignature 的 debugDescription 上打出了是否 special struct,只能通過這字串判斷。所以最終的處理是,在非 arm64 下,是 special struct 就走 _objc_msgForward_stret,否則走 _objc_msgForward。

static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
    IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
    Method method = class_getInstanceMethod(self.class, selector);
    const char *encoding = method_getTypeEncoding(method);
    BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
    if (methodReturnsStructValue) {
        @try {
            NSUInteger valueSize = 0;
            NSGetSizeAndAlignment(encoding, &valueSize, NULL);

            if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
                methodReturnsStructValue = NO;
            }
        } @catch (__unused NSException *e) {}
    }
    if (methodReturnsStructValue) {
        msgForwardIMP = (IMP)_objc_msgForward_stret;
    }
#endif
    return msgForwardIMP;
}

複製程式碼

Aspects 是判斷方法返回值的記憶體大小,來決定是否使用_objc_msgForward_stret

Aspects基本原理

  1. 將hook的selector指向objc_msgForward / _objc_msgForward_stret
  2. 生成aliasSelector指向原來的selector的IMP。
  3. forwardInvocation指向自定義的__ASPECTS_ARE_BEING_CALLED__
  4. 生成__aspects_forwardInvocation指向原來forwardInvocation的IMP

Screen Shot 2019-04-21 at 7.15.40 PM.png

Aspects原始碼分析

有前面的鋪墊,再看原始碼就會輕鬆很多,原始碼只會分析一些比較重要的部分。

Aspects一些內部結構和協議

這裡只簡單介紹一下,詳細結構可以檢視原始碼。

  • AspectToken:用於登出 Hook。
  • AspectInfo:block的第一個引數。
  • AspectIdentifier:每進行一個hook,都會生成一個AspectIdentifier物件,包含:方法,block,簽名資訊等。
  • AspectsContainer:用於盛放AspectIdentifier物件。一個物件或者類對應一個AspectsContainer物件,有三個陣列,beforeAspects,insteadAspects,afterAspects。
  • AspectTracker:每一個class對應一個AspectTracker。 在一個繼承鏈上一個selector只能被hook一次。

_AspectBlock

因為沒法直接拿到block的簽名資訊,所以建立_AspectBlock目的是拿到block的簽名資訊,然後就可以使用NSInvocation呼叫這個block。

我們先來看看蘋果原始碼Block_private.h Block記憶體結構,是一個結構體。

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

複製程式碼
// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

複製程式碼

再來看看_AspectBlock,可以很清晰的看到Aspects仿照系統定義的。

typedef NS_OPTIONS(int, AspectBlockFlags) {
    // 捕獲外界變數
    AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
    // 方法有簽名資訊,Block也有簽名資訊
    AspectBlockFlagsHasSignature          = (1 << 30)
};
typedef struct _AspectBlock {
    __unused Class isa;
    AspectBlockFlags flags;
    __unused int reserved;
    void (__unused *invoke)(struct _AspectBlock *block, ...);
    struct {
        unsigned long int reserved;
        unsigned long int size;
        // requires AspectBlockFlagsHasCopyDisposeHelpers
        void (*copy)(void *dst, const void *src);
        void (*dispose)(const void *);
        // requires AspectBlockFlagsHasSignature
        const char *signature;
        const char *layout;
    } *descriptor;
    // imported variables
} *AspectBlockRef;

複製程式碼

aspect_blockMethodSignature方法是用來獲得blocK的簽名。原理是因為沒法直接拿到block的簽名資訊,所以將block強制型別轉換為AspectBlockRef,根據標誌位和結構體的結構,獲取signature


static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    // 將block強制轉為AspectBlockRef
    AspectBlockRef layout = (__bridge void *)block;
	if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    // descriptor,指標移動
	void *desc = layout->descriptor;
	desc += 2 * sizeof(unsigned long int);
	// 如果捕獲外界變數
	if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
		desc += 2 * sizeof(void *);
    }
	if (!desc) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
	const char *signature = (*(const char **)desc);
	// 根據型別編碼獲得簽名資訊
	return [NSMethodSignature signatureWithObjCTypes:signature];
}
複製程式碼

這裡稍微解釋指標移動,我們知道指標是指向一塊記憶體的首地址,desc += 2 * sizeof(unsigned long int);是因為需要偏移下面兩個unsigned long int)記憶體大小。

unsigned long int reserved;     
unsigned long int size;
複製程式碼

如果捕獲外界變數,這兩個Void指標是有值得,所以需要偏移兩個Void指標的記憶體大小desc += 2 * sizeof(void *);

void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);

複製程式碼

我們看一看block簽名資訊是什麼樣

[UIViewController aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info){
        NSLog(@"viewDidLoad");
} error:nil];

複製程式碼

Screen Shot 2019-04-20 at 9.15.33 PM.png

返回值是Void,第一個引數是@?,表示是Block,第二個引數@"<AspectInfo>",表示遵循了AspectInfo協議,我們看到block簽名和方法簽名是不同的,所以需要比較簽名資訊。

aspect_isCompatibleBlockSignature

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
    NSCParameterAssert(blockSignature);
    NSCParameterAssert(object);
    NSCParameterAssert(selector);

    BOOL signaturesMatch = YES;
    // viewWillAppear: (v @ : c)
    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
    // block簽名引數一定是小於方法簽名引數
    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        if (blockSignature.numberOfArguments > 1) {
            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
            // 遵循AspectInfo協議物件
            if (blockType[0] != '@') {
                signaturesMatch = NO;
            }
        }
        // Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2.
        // The block can have less arguments than the method, that's ok.
        if (signaturesMatch) {
            for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
                const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
                const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
                // Only compare parameter, not the optional type data.
                if (!methodType || !blockType || methodType[0] != blockType[0]) {
                    signaturesMatch = NO; break;
                }
            }
        }
    }

    if (!signaturesMatch) {
        NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
        AspectError(AspectErrorIncompatibleBlockSignature, description);
        return NO;
    }
    return YES;
}

複製程式碼

從註釋可以看到簽名資訊引數前兩位是預設的,Argument 0self/blockargument 1SEL or id<AspectInfo>,所以從index = 2開始校驗。

aspect_add


// 這裡的self可以是例項物件 也可以是類物件
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);
    NSCParameterAssert(block);

    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        // 判斷selector是否允許進行hook操作
        // 1."retain""release""autorelease""forwardInvocation"這幾個方法是不被允許的。
        // 2.如果方法是dealloc,則他的切入點必須是Before。
        // 3.判斷當前例項物件和類物件是否能響應方法。
        // 4.是否是類物件,如果是則判斷繼承體系中方法是否已經被Hook,而例項則不用。
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            
            // 通過objc_getAssociatedObject 獲取selector方法的容器
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            
            // 將block封裝到AspectIdentifier物件中
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                // 通過options將identifier新增到容器對應的beforeAspects,insteadAspects,afterAspects陣列中
                [aspectContainer addAspect:identifier withOptions:options];
                // HookSelector和HookClass
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}
複製程式碼

aspect_prepareClassAndHookSelector

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    
    // Hook Class,進行swizzleForwardInvocation
    // klass類為剛建立的具有_Aspects_字尾的子類
    Class klass = aspect_hookClass(self, error);
    // 在建立的時候指定類他的父類,所以我們可以獲取到selector這個方法
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    // 如果selector的實現是_objc_msgForward或者_objc_msgForward_stret,就不進行method swizzle 了。
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // 獲得原生方法的型別編碼
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        // 給原selector方法名新增字首,並返回。 這個新增字首的selector的實現就是原selector的實現
        SEL aliasSelector = aspect_aliasForSelector(selector);
        if (![klass instancesRespondToSelector:aliasSelector]) {
            // 沒有使用aliasSelector 儲存selector原來的實現
            __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);
        }

        // 將被hook方法的實現改為forwardInvocation(訊息轉發)
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}

複製程式碼

總結一下:

  1. forwardInvocation的實現替換為 自定義方法__ASPECTS_ARE_BEING_CALLED__,並新增 __aspects_forwardInvocation的實現為forwardInvocation原來的實現。 需要進行訊息轉發的selector都會執行__ASPECTS_ARE_BEING_CALLED__
  2. hookselector的實現替換為_objc_msgForward 或者_objc_msgForward_stret,同時新增aspect_aliasForSelector的實現為selector原來的實現。 此時呼叫selector時就會進行訊息轉發。

aspect_hookClass

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    // 當self是instance(物件)獲取當前Class(類物件),當self是Class(類物件)返回自身
	Class statedClass = self.class;
    // 獲取isa指標
	Class baseClass = object_getClass(self);
	NSString *className = NSStringFromClass(baseClass);

    // 當hook一個物件的selector時會生成一個子類,子類字首就是AspectsSubclassSuffix。當self對應的類就是生成的子類,直接返回
	if ([className hasSuffix:AspectsSubclassSuffix]) {
		return baseClass;
    // 判斷是否為類物件,如果是,則直接在當前類中進行swizzle
	}else if (class_isMetaClass(baseClass)) {
        return aspect_swizzleClassInPlace((Class)self);
    // 判斷是否為KVO過的物件,因為KVO的物件ISA指標指向一箇中間類,則直接在這個間接勒種進行swizzle
    }else if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }

    /** 當hook一個物件的selector時,實現原理與KVO相似。 1,生成一個子類;2,aspect_swizzleForwardInvocation*/
    // 預設情況下,動態建立子類,拼接子類字尾為AspectsSubclassSuffix
	const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    // 獲取子類isa
	Class subclass = objc_getClass(subclassName);

	if (subclass == nil) {
		subclass = objc_allocateClassPair(baseClass, subclassName, 0);
		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__
		aspect_swizzleForwardInvocation(subclass);
        // 把生成子類的isa指標指向原生的類
		aspect_hookedGetClass(subclass, statedClass);
        // 把生成子類的元類的isa指向原生的類
		aspect_hookedGetClass(object_getClass(subclass), statedClass);
        // 註冊當前生成的子類
		objc_registerClassPair(subclass);
	}

    // 將當前物件的isa指標指向剛生成的類
	object_setClass(self, subclass);
	return subclass;
}
複製程式碼

總結一下:

  1. 動態建立子類
  2. 將子類的forwardInvocation的實現替換成__ASPECTS_ARE_BEING_CALLED__
  3. 把子類的元類的isa和子類的元類的isa指向原生的類
  4. 註冊子類
  5. self物件isa指標指向子類

圖片來自Aspects關聯&呼叫流程淺析
對某個類的所有例項進行hook

9525982-0e4975d5bebf2706.png

某個類例項進行hook

9525982-5e11919e4b1af4e7.png

ASPECTS_ARE_BEING_CALLED

// 訊息經過轉發後都會來到這裡(這裡包括手動訊息轉發和自動訊息轉發)在這裡進行統一的處理: 呼叫block,執行原方法實現
// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    // 拿到originalSelector
    SEL originalSelector = invocation.selector;
    // originalSelector 加字首得到 aliasSelector,含有字首的方法aspects_
	SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    // 用 aliasSelector 替換 invocation.selector
    invocation.selector = aliasSelector;
    // Instance 的容器
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    // Class 的容器
    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 {
        // 沒有Instead hooks時就執行selector 被hook之前的實現。
        Class klass = object_getClass(invocation.target);
        // 遍歷 invocation.target及其superClass找到例項可以響應 aliasSelector的invocation invoke
        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);

    // 呼叫一個沒有實現的selector會觸發 自動訊息轉發,在這種情況下整個繼承鏈中都不會響應aliasSelector也就導致respondsToAlias=false, 開始執行下面的方法
    // If no hooks are installed, call original implementation (usually to throw an exception)
    if (!respondsToAlias) {
        invocation.selector = originalSelector;
        SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
        // 如果實現了forwardInvocation,執行原來的訊息轉發,否則呼叫doesNotRecognizeSelector,丟擲異常。
        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 佇列中的 AspectIdentifier,執行 remove
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

複製程式碼

iOS Aspects原始碼剖析
訊息轉發機制與Aspects原始碼解析
NSMethodSignature
你真的會判斷 _objc_msgForward_stret 嗎
Aspects原始碼解析
從 Aspects 原始碼中我學到了什麼?
Aspects關聯&呼叫流程淺析

相關文章