iOS AOP框架Aspects實(shí)現(xiàn)原理

前言

眾所周知,Aspects框架運(yùn)用了AOP(面向切面編程)的思想,這里解釋下AOP的思想:AOP是針對(duì)業(yè)務(wù)處理過(guò)程中的切面進(jìn)行提取,它所面對(duì)的是處理過(guò)程中的某個(gè)步驟或階段,以獲得邏輯過(guò)程中各部分之間低耦合性的隔離效果。也許大家用Aspects提供的兩個(gè)方法用著很爽,有沒(méi)有想揭開(kāi)它的神秘面紗,看一下它的廬山真面目?接下來(lái)主要講下Aspects的主要實(shí)現(xiàn)原理。


ruhua.gif

實(shí)現(xiàn)原理

其實(shí)主要涉及到兩點(diǎn),用過(guò)runtime的同學(xué)都知道swizzling和消息轉(zhuǎn)發(fā),如果還不是很清楚的同學(xué)可以看下我之前寫(xiě)的一篇文章OC中swizzling的“移魂大法”,“移魂大法”我這邊就不累贅了,提下消息轉(zhuǎn)發(fā)。

消息轉(zhuǎn)發(fā)

MessageForward.jpg

當(dāng)向一個(gè)對(duì)象發(fā)送消息的時(shí)候,在這個(gè)對(duì)象的類(lèi)繼承體系中沒(méi)有找到該方法的時(shí)候,就會(huì)Crash掉,拋出* unrecognized selector sent to …異常信息,但是在拋出這個(gè)異常前其實(shí)還是可以拯救的,Crash之前會(huì)依次執(zhí)行下面的方法:

  1. resolveInstanceMethod (或resolveClassMethod):實(shí)現(xiàn)該方法,可以通過(guò)class_addMethod添加方法,返回YES的話(huà)系統(tǒng)在運(yùn)行時(shí)就會(huì)重新啟動(dòng)一次消息發(fā)送的過(guò)程,NO的話(huà)會(huì)繼續(xù)執(zhí)行下一個(gè)方法。

2.forwardingTargetForSelector:實(shí)現(xiàn)該方法可以將消息轉(zhuǎn)發(fā)給其他對(duì)象,只要這個(gè)方法返回的不是nil或self,也會(huì)重啟消息發(fā)送的過(guò)程,把這消息轉(zhuǎn)發(fā)給其他對(duì)象來(lái)處理。

  1. methodSignatureForSelector:會(huì)去獲取一個(gè)方法簽名,如果沒(méi)有獲取到的話(huà)就回直接挑用doesNotRecognizeSelector,如果能獲取的話(huà)系統(tǒng)就會(huì)創(chuàng)建一個(gè)NSlnvocation傳給forwardInvocation方法。

  2. forwardInvocation:該方法是上一個(gè)步傳進(jìn)來(lái)的NSlnvocation,然后調(diào)用NSlnvocationinvokeWithTarget方法,轉(zhuǎn)發(fā)到對(duì)應(yīng)的Target

  3. doesNotRecognizeSelector:拋出unrecognized selector sent to …異常。

整體原理

上面講了幾種消息轉(zhuǎn)發(fā)的方法,Aspects主要是利用了forwardInvocation進(jìn)行轉(zhuǎn)發(fā),Aspects其實(shí)利用和kvo類(lèi)似的原理,通過(guò)動(dòng)態(tài)創(chuàng)建子類(lèi)的方式,把對(duì)應(yīng)的對(duì)象isa指針指向創(chuàng)建的子類(lèi),然后把子類(lèi)的forwardInvocationIMP替成__ASPECTS_ARE_BEING_CALLED__,假設(shè)要hook的方法名XX,在子類(lèi)中添加一個(gè)Aspects_XX的方法,然后將Aspects_XXIMP指向原來(lái)的XX方法的IMP,這樣方便后面調(diào)用原始的方法,再把要hook的方法XXIMP指向_objc_msgForward,這樣就進(jìn)入了消息轉(zhuǎn)發(fā)流程,而forwardInvocationIMP被替換成了__ASPECTS_ARE_BEING_CALLED__,這樣就會(huì)進(jìn)入__ASPECTS_ARE_BEING_CALLED__進(jìn)行攔截處理,這樣整個(gè)流程大概結(jié)束。

代碼解析

代碼解析這塊的話(huà)主要講下核心的模塊,一些邊邊角角的東西去看了自然就明白了。

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.
};

AspectOptions提供各種姿勢(shì),前插、后插、整個(gè)方法替換都行,簡(jiǎn)直就是爽。

static void aspect_performLocked(dispatch_block_t block) {
    static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
    OSSpinLockLock(&aspect_lock);
    block();
    OSSpinLockUnlock(&aspect_lock);
}

aspect_add、aspect_remove方法里面用了aspect_performLocked, 而aspect_performLocked方法用了OSSpinLockLock加鎖機(jī)制,保證線(xiàn)程安全并且性能高。不過(guò)這種鎖已經(jīng)不在安全,主要原因發(fā)生在低優(yōu)先級(jí)線(xiàn)程拿到鎖時(shí),高優(yōu)先級(jí)線(xiàn)程進(jìn)入忙等(busy-wait)狀態(tài),消耗大量 CPU 時(shí)間,從而導(dǎo)致低優(yōu)先級(jí)線(xiàn)程拿不到 CPU 時(shí)間,也就無(wú)法完成任務(wù)并釋放鎖。這種問(wèn)題被稱(chēng)為優(yōu)先級(jí)反轉(zhuǎn),有興趣的可以點(diǎn)擊任意門(mén)不再安全的 OSSpinLock

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);

    // Already subclassed
    if ([className hasSuffix:AspectsSubclassSuffix]) {
        return baseClass;

        // We swizzle a class object, not a single object.
    }else if (class_isMetaClass(baseClass)) {
    
        return aspect_swizzleClassInPlace((Class)self);
        // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
    }else if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }

    // 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);
        if (subclass == nil) {
            NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }

        aspect_swizzleForwardInvocation(subclass);
        aspect_hookedGetClass(subclass, statedClass);
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        objc_registerClassPair(subclass);
    }

    object_setClass(self, subclass);
    return subclass;
}

aspect_hookClass這個(gè)方法顧名思義就是要hook對(duì)應(yīng)的Class,這在里創(chuàng)建子類(lèi),通過(guò)aspect_swizzleForwardInvocation方法把forwardInvocation的實(shí)現(xiàn)替換成__ASPECTS_ARE_BEING_CALLED__, aspect_hookedGetClass修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回當(dāng)前對(duì)象的 class,最后通過(guò)object_setClass把isa指針指向subclass

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    Class klass = aspect_hookClass(self, error);
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    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));
    }
}

通過(guò)aspect_isMsgForwardIMP方法判斷要hook的方法時(shí)候被hook了,這里如果有使用JSPatch的話(huà)會(huì)沖突掉,不過(guò)JSPatch已經(jīng)被蘋(píng)果禁止使用了,蘋(píng)果爸爸說(shuō)的算。如果沒(méi)有被hook走的話(huà)會(huì)先創(chuàng)建一個(gè)有自己別名的方法,然后把這個(gè)帶有別名方法的IMP指向原始要hook方法的實(shí)現(xiàn),最后要hook的方法的IMP指向_objc_msgForward或者_objc_msgForward_stret,為什么還會(huì)有一個(gè)_objc_msgForward_stret,詳細(xì)原因可以參考JSPatch實(shí)現(xiàn)原理詳解<二>里面的Special Struct

// 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)];
}

該函數(shù)為swizzle后, 實(shí)現(xiàn)新IMP統(tǒng)一處理的核心方法 , 完成一下幾件事:

  • 處理調(diào)用邏輯, 有before, instead, after, remove四種option
  • 將block轉(zhuǎn)換成一個(gè)NSInvocation對(duì)象以供調(diào)用。
  • AspectsContainer根據(jù)aliasSelector取出對(duì)象, 并組裝一個(gè)AspectInfo, 帶有原函數(shù)的調(diào)用參數(shù)和各項(xiàng)屬性, 傳給外部的調(diào)用者 (在這是block) .
  • 調(diào)用完成后銷(xiāo)毀帶有removeOptionhook邏輯, 將原selector掛鉤到原IMP上, 刪除別名selector

總結(jié)

-forwardInvocation:的消息轉(zhuǎn)發(fā)雖然看起來(lái)很神奇,但是平時(shí)沒(méi)什么需求的話(huà)盡量不要去碰這個(gè)東西,一般的話(huà)swizzling基本可以滿(mǎn)足我們項(xiàng)目的大部分需求了,還有就是JSPatch既然不能用了,但是Aspects這個(gè)框架還是正常上AppStore的,利用這個(gè)庫(kù)的話(huà)還是可以弄出輕量級(jí)Hotfix,感興趣的同學(xué)可以點(diǎn)擊輕量級(jí)低風(fēng)險(xiǎn) iOS 熱更新方案

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

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

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