iOS開(kāi)發(fā)中的AOP利器 - Aspects 源碼分析(二)

執(zhí)行hook事件

Aspects源碼分析的第一篇文章中主要分析了為hook做的準(zhǔn)備工作,接下來(lái)分析一下腔剂,當(dāng) selector執(zhí)行時(shí)是如何執(zhí)行你自己添加的自定義hook事件的服傍。

通過(guò)hook準(zhǔn)備工作的處理后 ,外界調(diào)用的hook selector 會(huì)直接進(jìn)入消息轉(zhuǎn)發(fā)執(zhí)行到方法forwardInvocation: ,然后此時(shí)forwardInvocation:方法的IMP是指向處理hook的函數(shù) __ASPECTS_ARE_BEING_CALLED__,這個(gè)函數(shù)也是整個(gè)hook事件的核心函數(shù)。代碼實(shí)現(xiàn)如下

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]; //aliasSelector 已經(jīng)在 aspect_prepareClassAndHookSelector 函數(shù)中替換為原來(lái)selector的實(shí)現(xiàn) , 這里就是調(diào)回原方法的實(shí)現(xiàn)代碼
                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)];
}

這個(gè)函數(shù)首先把傳進(jìn)來(lái)的NSInvocation對(duì)象的selector 賦值為 IMP指向調(diào)用方法的原IMPaliasSelector , 這樣可以方便調(diào)用會(huì)原方法的IMP的實(shí)現(xiàn)。

獲取hook事件容器

AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];

這里是通過(guò)aliasSelector分別取出綁定在 hook對(duì)象 以及 hook class (hook對(duì)象的isa指針指向的Class)中對(duì)應(yīng)的容器對(duì)象AspectsContainer , 并生成一個(gè) AspectInfo對(duì)象普舆,用于封裝執(zhí)行方法及hook事件是所需的實(shí)參。接下來(lái)分別是遍歷兩個(gè)容器對(duì)象中的三個(gè)數(shù)組(beforeAspects 池磁、insteadAspects 奔害、afterAspects)是否有 hook的標(biāo)識(shí)對(duì)象AspectIdentifier , 如果有的話(huà)就執(zhí)行相應(yīng)的hook事件。insteadAspects如果這個(gè)數(shù)組有對(duì)象存放地熄,就說(shuō)明原方法的實(shí)現(xiàn)被替換為執(zhí)行 insteadAspects里的hook事件了。

hook執(zhí)行

//執(zhí)行hook
aspect_invoke(classContainer.beforeAspects, info);

//hook執(zhí)行的宏代碼
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
    [aspect invokeWithInfo:info];\
    if (aspect.options & AspectOptionAutomaticRemoval) { \
        aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
    } \
}

- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
    //根據(jù)block得簽名字符串 芯杀, 生成對(duì)應(yīng)的消息調(diào)用對(duì)象端考。用來(lái)在設(shè)置完參數(shù)后調(diào)用block
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    //取出外界調(diào)用方法時(shí)雅潭,系統(tǒng)封裝的消息調(diào)用對(duì)象,用來(lái)獲取實(shí)參的值
    NSInvocation *originalInvocation = info.originalInvocation;
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;

    // Be extra paranoid. We already check that on hook registration.
    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.
    //這里設(shè)置Block的 第一個(gè)參數(shù)為傳進(jìn)來(lái)的AspectInfo對(duì)象 却特, 第0位置的參數(shù)是Block本身
    if (numberOfArguments > 1) { //有參數(shù)的話(huà)就吧第一個(gè)參數(shù) 設(shè)置為 AspectInfo 扶供, 第0位置是block本身。
        
         /**
         官方文檔解析 : When the argument value is an object, pass a pointer to the variable (or memory) from which the object should be copied
         &info : info對(duì)象指針的地址
         這樣傳參的目的是保證了裂明,參數(shù)無(wú)論是普通類(lèi)型參數(shù)還是對(duì)象都可以通過(guò)你傳進(jìn)來(lái)的指針椿浓,通過(guò)拷貝指針指向的內(nèi)容來(lái)獲取到 普通類(lèi)型數(shù)據(jù) 或者 對(duì)象指針。
         */
        [blockInvocation setArgument:&info atIndex:1];
    }
    
    void *argBuf = NULL;
    //遍歷參數(shù)類(lèi)型typeStr , 為blockInvocation對(duì)應(yīng)的參數(shù)創(chuàng)建所需空間 闽晦, 賦值數(shù)據(jù) 扳碍, 設(shè)置blockInvocation參數(shù)
    for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
        const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
        NSUInteger argSize; //實(shí)參多需要的空間大小
        NSGetSizeAndAlignment(type, &argSize, NULL); //根據(jù)encodeType 字符串 創(chuàng)建對(duì)應(yīng)空間存放block的參數(shù)數(shù)據(jù)所屬要的size
        
        if (!(argBuf = reallocf(argBuf, argSize))) { //創(chuàng)建size大小的空間
            AspectLogError(@"Failed to allocate memory for block invocation.");
            return NO;
        }
        
        [originalInvocation getArgument:argBuf atIndex:idx]; //獲取到指向?qū)?yīng)參數(shù)的指針
        [blockInvocation setArgument:argBuf atIndex:idx]; //把指向?qū)?yīng)實(shí)參指針的地址(相當(dāng)于指向?qū)崊⒅羔樀闹羔槪﹤鹘oinvocation 進(jìn)行拷貝,得到的就是指向?qū)崊?duì)象的指針
    }
    
    [blockInvocation invokeWithTarget:self.block]; //設(shè)置完實(shí)參執(zhí)行block
    
    if (argBuf != NULL) {
        free(argBuf); //c語(yǔ)言的創(chuàng)建空間 仙蛉,用完后需要釋放笋敞,關(guān)于c語(yǔ)言的動(dòng)態(tài)內(nèi)存相關(guān)資料可以看 https://blog.csdn.net/qq_29924041/article/details/54897204
    }
    return YES;
}

可以看出 AspectIdentifier-invokeWithInfo是執(zhí)行hook事件最終的方法荠瘪。該方法主要處理的事情是:根據(jù)傳進(jìn)來(lái)的AspectInfo對(duì)象為最初定義hook事件的Block設(shè)置相應(yīng)的參數(shù)夯巷。并執(zhí)行Block(hook事件)

blockInvocation設(shè)置參數(shù)解析

  • 設(shè)置了block的第一個(gè)位置的參數(shù)為AspectInfo * info , 這樣做及未來(lái)方便內(nèi)部遍歷設(shè)置參數(shù) (與selector保持一致,自定義參數(shù)從 索引為2的位置開(kāi)始)哀墓,又方便了外界在定義hook的事件是獲取到實(shí)例對(duì)象 - [info instance]

  • getArgument:atIndex:返回的是對(duì)應(yīng)索引參數(shù)的指針(地址)趁餐。假如參數(shù)是一個(gè)對(duì)象指針的話(huà),會(huì)返回對(duì)象的指針地址篮绰。而setArgument:atIndex:會(huì)把傳進(jìn)來(lái)的參數(shù)(指針)拷貝其指向的內(nèi)容到相應(yīng)的索引位置中澎怒。所以argBuf在整個(gè)for循環(huán)中可以不斷地使用同一個(gè)指針并不斷的reallocf返回指向一定堆空間的指針。argBuf指針只是作為一個(gè)設(shè)置參數(shù)的中介阶牍,每一個(gè)for循環(huán)后setArgument :atIndex:都會(huì)把argBuf指向的內(nèi)容拷貝到invocation中喷面。

hook的移除

AspectIdentifierremove方法,會(huì)調(diào)用到下面的函數(shù)

static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) {
    NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type.");

    __block BOOL success = NO;
    aspect_performLocked(^{
        id self = aspect.object; // strongify
        if (self) {
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
            success = [aspectContainer removeAspect:aspect]; //重container的 三個(gè)數(shù)組中移除aspect

            aspect_cleanupHookedClassAndSelector(self, aspect.selector);
            // destroy token
            aspect.object = nil;
            aspect.block = nil;
            aspect.selector = NULL;
        }else {
            NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
            AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
        }
    });
    return success;
}

1. 移除AspectContainer中的AspectIdentifier

首先獲取被hook的對(duì)象中通過(guò)runtime綁定的關(guān)聯(lián)屬性 ---AspectsContainer *aspectContainer ,并分別移除數(shù)組的hook標(biāo)識(shí)對(duì)象 - AsepctIdentifier * aspect,實(shí)現(xiàn)代碼如下:

//AspectContainer的實(shí)例方法
- (BOOL)removeAspect:(id)aspect {
    for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
                                        NSStringFromSelector(@selector(insteadAspects)),
                                        NSStringFromSelector(@selector(afterAspects))]) {
        NSArray *array = [self valueForKey:aspectArrayName];
        NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
        if (array && index != NSNotFound) {
            NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
            [newArray removeObjectAtIndex:index];
            [self setValue:newArray forKey:aspectArrayName];
            return YES;
        }
    }
    return NO;
}

2.還原selector指向的IMP

// Check if the method is marked as forwarded and undo that.
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (aspect_isMsgForwardIMP(targetMethodIMP)) {
    // Restore the original method implementation.
    const char *typeEncoding = method_getTypeEncoding(targetMethod);
    SEL aliasSelector = aspect_aliasForSelector(selector);
    Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
    IMP originalIMP = method_getImplementation(originalMethod);
    NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);

    class_replaceMethod(klass, selector, originalIMP, typeEncoding);
    AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}

在進(jìn)行hook準(zhǔn)備工作室走孽,把selector的IMP修改成立進(jìn)入消息轉(zhuǎn)發(fā)的惧辈,并且添加了一個(gè)新的selectorasepct__selector)指向原selectorIMP這里是還原selector的指向。

3.移除AspectTracker對(duì)應(yīng)的記錄

static void aspect_deregisterTrackedSelector(id self, SEL selector) {
    if (!class_isMetaClass(object_getClass(self))) return;

    NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
    NSString *selectorName = NSStringFromSelector(selector);
    Class currentClass = [self class];
    do {
        AspectTracker *tracker = swizzledClassesDict[currentClass];
        if (tracker) {
            [tracker.selectorNames removeObject:selectorName];
            if (tracker.selectorNames.count == 0) {
                [swizzledClassesDict removeObjectForKey:tracker];
            }
        }
    }while ((currentClass = class_getSuperclass(currentClass)));
}

如果被hook的是類(lèi)(調(diào)用的是類(lèi)方法添加hook)磕瓷。在全局對(duì)象(NSMutableDictionary *swizzledClassesDict)中移除hook class的整個(gè)向上繼承關(guān)系鏈上的AspectTracker中的selectors數(shù)組中的selectorName字符串盒齿。

4.還原被hook的實(shí)例對(duì)象的isa的指向 + 還原被hook Class的forwardInvocation:方法的IMP指向

// Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
AspectsContainer *container = aspect_getContainerForObject(self, selector);
if (!container.hasAspects) {
    // Destroy the container
    aspect_destroyContainerForObject(self, selector);

    // Figure out how the class was modified to undo the changes.
    NSString *className = NSStringFromClass(klass);
    if ([className hasSuffix:AspectsSubclassSuffix]) {
        Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
        NSCAssert(originalClass != nil, @"Original class must exist");
        object_setClass(self, originalClass); //把hook的類(lèi)對(duì)象isa 從_Aspects_class -> 原來(lái)的類(lèi)
        AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));

        // We can only dispose the class pair if we can ensure that no instances exist using our subclass.
        // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
        //objc_disposeClassPair(object.class);
    }else {
        // Class is most likely swizzled in place. Undo that.
        if (isMetaClass) {
            aspect_undoSwizzleClassInPlace((Class)self);
        }
    }
}

這里首先判斷hook對(duì)象的AspectContainer屬性數(shù)組中是否還有 AspectIndetafier對(duì)象(hook標(biāo)識(shí))。如果沒(méi)有的話(huà)就清除掉該對(duì)象的關(guān)聯(lián)屬性容器困食。接下來(lái)分兩種情況進(jìn)行還原處理

情況1. 被hook的是普通實(shí)例對(duì)象 : 此時(shí)需要把對(duì)象的isa指向還原會(huì)為原來(lái)的Class --> object_setClass(self, originalClass);

情況2. 被hook的是類(lèi): 還原forwardInvocation:方法的IMP指向?yàn)樵瓉?lái)的進(jìn)入消息轉(zhuǎn)發(fā)的IMP边翁,并且從全局變量swizzledClasses中移除類(lèi)名字符串的記錄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末硕盹,一起剝皮案震驚了整個(gè)濱河市符匾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘩例,老刑警劉巖啊胶,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甸各,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡焰坪,警方通過(guò)查閱死者的電腦和手機(jī)趣倾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)某饰,“玉大人儒恋,你說(shuō)我怎么就攤上這事∏” “怎么了诫尽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瘟仿。 經(jīng)常有香客問(wèn)我箱锐,道長(zhǎng),這世上最難降的妖魔是什么劳较? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任驹止,我火速辦了婚禮,結(jié)果婚禮上观蜗,老公的妹妹穿的比我還像新娘臊恋。我一直安慰自己,他們只是感情好墓捻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布抖仅。 她就那樣靜靜地躺著,像睡著了一般砖第。 火紅的嫁衣襯著肌膚如雪撤卢。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天梧兼,我揣著相機(jī)與錄音放吩,去河邊找鬼。 笑死羽杰,一個(gè)胖子當(dāng)著我的面吹牛渡紫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播考赛,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼惕澎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了颜骤?” 一聲冷哼從身側(cè)響起唧喉,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后欣喧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體腌零,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梯找,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年唆阿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锈锤。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驯鳖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出久免,到底是詐尸還是另有隱情浅辙,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布阎姥,位于F島的核電站记舆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏呼巴。R本人自食惡果不足惜泽腮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衣赶。 院中可真熱鬧诊赊,春花似錦、人聲如沸府瞄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遵馆。三九已至鲸郊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間货邓,已是汗流浹背秆撮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逻恐,地道東北人像吻。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像复隆,于是被迫代替她去往敵國(guó)和親拨匆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容