Aspects AOP 的實現

都傳聞說 OC 的運行時很是NB,今天就來看看很是有名的Aspects,源碼在這git

https://github.com/steipete/A...github

裏面的內容很是簡單,其實就2個文件,Aspect.hAspect.m,它使用CategoryNSObject提供了兩個額外的方法,API以下:objective-c

/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

/// Deregister an aspect.
/// @return YES if deregistration is successful, otherwise NO.
id<AspectToken> aspect = ...;
[aspect remove];

它提供的解決方案就是爲一個消息提供一個 before 和 after 的 block 調用,也就是爲 OC 提供了 AOP 的能力。
咱們知道在 Java 中,實現 AOP 採用的是動態代理的方式,那麼在 OC 中的實現,其實就是經過 Swizzle Method 的方式進行啦。ide

一探究竟

看下 Aspects 究竟是如何實現這個功能的函數

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error {
    return aspect_add((id)self, selector, options, block, error);
}

/// @return A token which allows to later deregister the aspect.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error {
    return aspect_add(self, selector, options, block, error);
}

事實上,無論是靜態仍是動態方式添加,都是使用aspect_add這個方法,代理

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);
    NSCParameterAssert(block);

    __block AspectIdentifier *identifier = nil;

    // ...
    // 省略加鎖的 block 和 權限檢查

    // 看 aspect_getContainerForObject 源碼可知,使用 lazy load 的方式,爲 self 生成一個 AspectsContainer
    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;
}

好了,這裏的大頭是aspect_prepareClassAndHookSelector指針

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);

    // 下面一行代碼,是動態生成了一個子類,而後覆蓋了原先的 forwardInvocation 消息,這是這裏最 magic 的地方,如下會講到
    Class klass = aspect_hookClass(self, error);
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);

    // 檢查若是目標方法的實現還不是 _objc_msgForward 或者 _objc_msgForward_stret 的話,就進行 hook
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, it not already copied.
        const char *typeEncoding = method_getTypeEncoding(targetMethod);

        // 給咱們須要被取代的 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);
        }

        // 把原先的 selector 方法的實現指向 _objc_msgForward 或 _objc_msgForward_stret。
        // 在先前調用了 aspect_hookClass 裏面,hook 了 forwardInvocation,後文會說明
        // We use forwardInvocation to hook in.
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}

好了,通過這麼多步驟後,咱們理清一下思路,若是咱們要對@selector(viewDidLoad:)進行 hookcode

  1. 先建立 subclass, hook @selector(forwardInvocation:),並把當前的對象設置爲該類的對象,這樣在不污染原類的狀況下,實現了forwardInvocation的hookorm

  2. 爲咱們的 obj 的目標消息建立一個別名,這裏若是是viewDidLoad:的話,那別名就是aspects_viewDidLoad:對象

  3. 取代目標消息的實現,使用aspect_getMsgForwardIMP來選擇是 _objc_msgForward 或是 _objc_msgForward_stret

這樣就完成了前置工做,接下來咱們簡單來說什麼是 _objc_msgForward

Objc 消息轉發簡介

咱們這篇,主要是講 Aspects 提供的解決方案,因此不會展開闡述 objc runtime 的一些內容,因此先提供參考資料:

http://blog.ibireme.com/2013/...

objc 中發送消息的方式是主要是在 C 層面調用 obj_msgSend 方法,若是找不到消息的實現,它會嘗試進行轉發,原理是把函數的實現改成 _objc_msgForward,它是一個函數指針

在轉發過程當中,objc 會把方法簽名包裝成 Invocation 傳入到 forwardInvocation: 裏,如下是對博文的引用:

  • Test NSObject initialize

  • Test NSObject new

  • Test NSObject alloc

  • Test NSObject allocWithZone:

  • Test NSObject init

  • Test NSObject performSelector:

  • Test NSObject resolveInstanceMethod:

  • Test NSObject forwardingTargetForSelector:

  • Test NSObject methodSignatureForSelector:

  • Test NSObject class

  • Test NSObject doesNotRecognizeSelector:

結合NSObject文檔能夠知道,_objc_msgForward 消息轉發作了以下幾件事:

  1. 調用resolveInstanceMethod:方法,容許用戶在此時爲該Class動態添加實現。若是有實現了,則調用並返回。若是仍沒實現,繼續下面的動做。

  2. 調用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象。若是獲取到,則直接轉發給它。若是返回了nil,繼續下面的動做。

  3. 調用methodSignatureForSelector:方法,嘗試得到一個方法簽名。若是獲取不到,則直接調用doesNotRecognizeSelector拋出異常。

  4. 調用forwardInvocation:方法,將地3步獲取到的方法簽名包裝成Invocation傳入,如何處理就在這裏面了。
    上面這4個方法均是模板方法,開發者能夠override,由runtime來調用。最多見的實現消息轉發,就是重寫方法3和4,吞掉一個消息或者代理給其餘對象都是沒問題的。

通過以上,咱們知道了,若是方法的實現是_objc_msgForward的話,那咱們的消息就會被包裝成Invocation發送到forwardInvocation裏去,那麼在前面,咱們進行subclass的時候,就會forwardInvocation進行了hook,這時候就用到了!
看看 Aspects 是如何作的吧。

偷樑換柱

具體函數在aspect_swizzleForwardInvocation中實現

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

咱們看到,AspectsforwardInvocation的實現換成了__ASPECTS_ARE_BEING_CALLED__這個函數,而原始的forwardInvocation實現的名字就變成了__aspects_forwardInvocation
看看__ASPECTS_ARE_BEING_CALLED__這裏幹了什麼

// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);

    // 獲得原始 selector
    SEL originalSelector = invocation.selector;
    // 獲得原始 selector 的別名(以前被咱們添加到類裏了),這纔是真正的實現
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    // 替換 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);

    // 以上就是執行 hook 的具體內容了

    // If no hooks are installed, call original implementation (usually to throw an exception)
    if (!respondsToAlias) {
        // 若是沒有執行的話。。那麼只好執行默認的 forwardInvocation 了
        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)];
}

以上就是Aspects hook objc 進行 AOP 全過程,雖然只有短短一千行不到的代碼,卻提供了很方便的方式進行 AOP,實現也很巧妙。看完後對 objc runtime 羨慕不已,實在強大!

相關文章
相關標籤/搜索