Aspects源碼解讀與runtime

開篇

由于近期在搞優(yōu)化無侵入埋點(diǎn),在github上發(fā)現(xiàn)了一個(gè)基于swizzling method 的輕量級開源框架 Aspects,其總共只有一個(gè)類文件。為了加強(qiáng)了解,在這里寫下我學(xué)習(xí)和總結(jié)的心得。

基本原理

使用Aspects,能夠在一個(gè)方法前/后插入一段代碼,也能直接替換整個(gè)方法的實(shí)現(xiàn)。

這是怎么做到的呢?

我們都知道oc是一門動態(tài)語言,在執(zhí)行一個(gè)函數(shù)的時(shí)候,其實(shí)是在給對象發(fā)消息,根據(jù)函數(shù)名生成selector,通過selector找到指向具體函數(shù)實(shí)現(xiàn)的IMP,然后才執(zhí)行真正的函數(shù)邏輯。在這個(gè)基礎(chǔ)上,如果我們?nèi)藶榈母淖?code>selector與IMP的對應(yīng)關(guān)系,那就能讓原本的函數(shù)做一些其它的事情。我們先來看一下oc中類的結(jié)構(gòu):

在其中,我們現(xiàn)在需要關(guān)注的是objc_method_list **methodLists,這個(gè)方法鏈表內(nèi)儲存的是Method類型:

Method結(jié)構(gòu)體中包含一個(gè)SEL和IMP,實(shí)際上相當(dāng)于在SEL和IMP之間作了一個(gè)映射。有了SEL,我們便可以找到對應(yīng)的IMP,從而調(diào)用方法的實(shí)現(xiàn)代碼。

但是函數(shù)具體是怎么執(zhí)行的呢?見下圖(轉(zhuǎn)):


通過上圖我們可以發(fā)現(xiàn),在函數(shù)執(zhí)行的過程當(dāng)中,如果selector有對應(yīng)的IMP,則直接執(zhí)行,如果沒有,那么就會進(jìn)入消息轉(zhuǎn)發(fā)流程,依次有
resolveInstanceMethod
forwardingTargetForSelector
methodSignatureForSelector
forwardInvocation等4個(gè)函數(shù)。
Aspects選擇了在forwardInvocation這個(gè)階段處理是因?yàn)椋?br> resolvedInstanceMethod 適合給類/對象動態(tài)添加一個(gè)相應(yīng)的實(shí)現(xiàn),
forwardingTargetForSelector 適合將消息轉(zhuǎn)發(fā)給其他對象處理,相對而言,forwardInvocation 是里面最靈活,最能符合需求的。

因此 Aspects 的方案就是,對于待 hook 的 selector,將其指向 objc_msgForward / _objc_msgForward_stret ,同時(shí)生成一個(gè)新的 aliasSelector 指向原來的 IMP,并且 hook 住 forwardInvocation 函數(shù),使他指向自己的實(shí)現(xiàn)。按照上面的思路,當(dāng)被 hook 的 selector 被執(zhí)行的時(shí)候,首先根據(jù) selector 找到了 objc_msgForward / _objc_msgForward_stret ,而這個(gè)會觸發(fā)消息轉(zhuǎn)發(fā),從而進(jìn)入 forwardInvocation。同時(shí)由于 forwardInvocation 的指向也被修改了,因此會轉(zhuǎn)入新的forwardInvocation 函數(shù),在里面執(zhí)行需要嵌入的附加代碼,完成之后,再轉(zhuǎn)回原來的 IMP。

分析源碼

在看具體實(shí)現(xiàn)之前,我們需要了解Aspects類里面定義的幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu):

AspectOptions
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0,            /// Called after the original implementation (default)
    AspectPositionInstead = 1,            /// Will replace the original implementation.
    AspectPositionBefore  = 2,            /// Called before the original implementation.
    
    AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};

這里表示了block的執(zhí)行時(shí)機(jī),可以是在原函數(shù)之前、之后或者完全替換掉原函數(shù)。

AspectsContainer
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end

跟蹤對象的所有情況

AspectIdentifier
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end

一個(gè)具體的Aspect,其中包括了block主體、執(zhí)行時(shí)機(jī)options、方法SEL、方法參數(shù)、簽名。將我們傳入的block封裝成AspectIdentifier。

AspectInfo
@interface AspectInfo : NSObject <AspectInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end

內(nèi)部主要是NSInvocation信息,將其封裝了一層。

AspectTracker
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, readonly) NSString *trackedClassName;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@end

用于跟蹤類是否已經(jīng)被hook并給其上標(biāo)記,防止重復(fù)替換方法。

具體實(shí)現(xiàn)

我們從入口函數(shù)開始:

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];

                // Modify the class to allow message interception.
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}

這段函數(shù)總共做了哪些事情呢?
1.aspect_isSelectorAllowedAndTrack()對傳進(jìn)來的類和參數(shù)進(jìn)行判斷。利用黑名單來判斷傳進(jìn)來的類和參數(shù)能否被hook,比如對于某些方法(retain、releaseautorelease、forwardInvocation)是不能hook的,同時(shí)對于dealloc來說,只能把代碼插入到dealloc之前,還需要檢測類是否能響應(yīng)方法。另外對于類對象而言,還需要對類的層級結(jié)構(gòu)(父類、子類)進(jìn)行判斷,防止同一方法被hook多次。

2.使用aspect_getContainerForObject()創(chuàng)建一個(gè)AspectsContainer容器,其中用到了動態(tài)添加屬性。

static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    SEL aliasSelector = aspect_aliasForSelector(selector);
    AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
    if (!aspectContainer) {
        aspectContainer = [AspectsContainer new];
        objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
    }
    return aspectContainer;
}

3.使用+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;創(chuàng)建一個(gè)AspectsIdentifier并把其加入之前創(chuàng)建的AspectsContainer中去,由于我們使用Aspects是用block來替換方法的,所以就需要將我們傳入的block替換成具體的方法,所以AspectsIdentifier中就有一個(gè)NSMethodSignature *blockSignature;方法簽名屬性。具體的轉(zhuǎn)換方法如下:

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    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;
    }
    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];
}

其中AspectBlockRef是Aspects內(nèi)部自定義的一個(gè)block結(jié)構(gòu):

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;

在將block轉(zhuǎn)換成方法簽名之后,使用aspect_isCompatibleBlockSignature來判斷轉(zhuǎn)換出的方法簽名和替換方法的方法簽名參數(shù)等是否一致:

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
    BOOL signaturesMatch = YES;
    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        if (blockSignature.numberOfArguments > 1) {
            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
            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;
}

一般的方法都有兩個(gè)隱藏的參數(shù),第一個(gè)是self,第二個(gè)是SEL,所以要從之后開始比較。

4.真正核心的交換方法aspect_prepareClassAndHookSelector。當(dāng)forwardInvocation被hook之后,將對傳入的selector進(jìn)行hook,將selector指向了轉(zhuǎn)發(fā)IMP,同時(shí)生成一個(gè)aliasSelector,指向原來的IMP。

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    Class klass = aspect_hookClass(self, error);
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    //如果發(fā)現(xiàn) selector 已經(jīng)指向了轉(zhuǎn)發(fā) IMP ,那就就不需要進(jìn)行交換了
    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_hookClass方法來創(chuàng)建hook子類,在aspect_swizzleClassInPlace內(nèi)通過aspect_swizzleForwardInvocation方法將本類中的forwardInvocation替換成我們自定義的__ASPECTS_ARE_BEING_CALLED__方法,當(dāng)類沒找到消息處理時(shí),通過forwardInvocation轉(zhuǎn)到我們自定義的方法內(nèi),就可以統(tǒng)一處理了。

static Class aspect_swizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    NSString *className = NSStringFromClass(klass);
    //保證線程、數(shù)據(jù)安全
    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        if (![swizzledClasses containsObject:className]) {
            // 將原IMP指向forwardInvocation
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}
static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // 將__aspects_forwardInvocation:指向originalImplementation,
    // 將originalImplementation添加到Method,以便下次調(diào)用,直接就可以了    
    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));
}

5.轉(zhuǎn)發(fā)最終的邏輯代碼最終轉(zhuǎn)入 ASPECTS_ARE_BEING_CALLED 函數(shù)的處理中。

// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    SEL originalSelector = invocation.selector;
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    invocation.selector = aliasSelector;
    // 本對象的AspectsContainer,添加到對象的aspect
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    // 這個(gè)類的AspectsContainer,添加類上面的aspect
    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)];
}

首先,使用aliasSelector替換掉傳進(jìn)來invocationselector,之后將invocation轉(zhuǎn)換成AspectInfo,AspectInfo主要包含了一些參數(shù)數(shù)組等invocation的信息,為了獲取參數(shù)數(shù)組,Aspect為NSInvocation寫了一個(gè)分類。之后就轉(zhuǎn)到最終調(diào)用的地方了:

- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    NSInvocation *originalInvocation = info.originalInvocation;
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
    //參數(shù)錯(cuò)誤判斷
    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
        AspectLogError(@"Block has too many arguments. Not calling %@", info);
        return NO;
    }
    // The `self` of the block will be the AspectInfo. Optional.
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }
    
    void *argBuf = NULL;
    for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
        const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
        NSUInteger argSize;
        NSGetSizeAndAlignment(type, &argSize, NULL);
        
        if (!(argBuf = reallocf(argBuf, argSize))) {
            AspectLogError(@"Failed to allocate memory for block invocation.");
            return NO;
        }
        
        [originalInvocation getArgument:argBuf atIndex:idx];
        [blockInvocation setArgument:argBuf atIndex:idx];
    }
    
    [blockInvocation invokeWithTarget:self.block];
    
    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}

取到原invocation和待替換的block的invocation,判斷block的參數(shù)如果大于原參數(shù),輸出錯(cuò)誤信息。之后逐個(gè)替換參數(shù),然后執(zhí)行blockInvocation。到這整個(gè)aspect工作就全部執(zhí)行完了。

疑問

1、為什么aspect_invoke()要用宏定義?
2、

    // 本對象的AspectsContainer,添加到對象的aspect
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    // 這個(gè)類的AspectsContainer,添加類上面的aspect
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);

這兩句代碼是怎么執(zhí)行的?為什么會返回AspectsContainer?

3、最后invoke函數(shù)內(nèi),為什么只判斷block參數(shù)個(gè)數(shù)大于原函數(shù)參數(shù)是錯(cuò)誤的,bolck參數(shù)個(gè)數(shù)小于原函數(shù)參數(shù)個(gè)數(shù)時(shí),忽略原函數(shù)內(nèi)多出來的參數(shù),這樣做是為什么?

最后

Aspects的源碼陸陸續(xù)續(xù)一星期才看完,深深感覺到看的時(shí)候有些力不從心,大概是runtime基礎(chǔ)太薄弱了吧。同時(shí)感謝http://wereadteam.github.io/2016/06/30/Aspects/提供的幫助

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

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

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