iOS 如何實現Aspect Oriented Programming (下)

(接上篇)git

五. Aspects hook過程詳解

先看看函數調用棧的狀況github

- aspect_prepareClassAndHookSelector(self, selector, error);
  ├── aspect_hookClass(self, error)
  │    ├──aspect_swizzleClassInPlace
  │    ├──aspect_swizzleForwardInvocation
  │    │  └──__ASPECTS_ARE_BEING_CALLED__
  │    │       ├──aspect_aliasForSelector
  │    │       ├──aspect_getContainerForClass
  │    │       ├──aspect_invoke
  │    │       └──aspect_remove
  │    └── aspect_hookedGetClass
  ├── aspect_isMsgForwardIMP
  ├──aspect_aliasForSelector(selector)
  └── aspect_getMsgForwardIMP複製代碼

從調用棧能夠看出,Aspects hook過程主要分4個階段,hookClass,ASPECTS_ARE_BEING_CALLED,prepareClassAndHookSelector,remove。swift

1. hookClass
NSCParameterAssert(self);
 Class statedClass = self.class;
 Class baseClass = object_getClass(self);
 NSString *className = NSStringFromClass(baseClass);複製代碼

statedClass 和 baseClass是有區別的的。api

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

+ (Class)class {
    return self;
}複製代碼

statedClass 是獲取類對象,baseClass是獲取到類的isa。數組

// 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);
    }複製代碼

先判斷是用來className是否包含hasSuffix:AspectsSubclassSuffix安全

static NSString *const AspectsSubclassSuffix = @"_Aspects_";複製代碼

若是包含了@"_Aspects_"後綴,表明該類已經被hook過了,直接return。
若是不包含@"_Aspects_"後綴,再判斷是不是baseClass是不是元類,若是是元類,調用aspect_swizzleClassInPlace。若是也不是元類,再判斷statedClass 和 baseClass是否相等,若是不相等,說明爲KVO過的對象,由於KVO的對象isa指針會指向一箇中間類。對KVO中間類調用aspect_swizzleClassInPlace。數據結構

static Class aspect_swizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    NSString *className = NSStringFromClass(klass);

    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        if (![swizzledClasses containsObject:className]) {
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}複製代碼

_aspect_modifySwizzledClasses會傳入一個入參爲(NSMutableSet *swizzledClasses)的block,block裏面就是判斷在這個Set裏面是否包含當前的ClassName,若是不包含,就調用aspect_swizzleForwardInvocation()方法,並把className加入到Set集合裏面。函數

static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
    static NSMutableSet *swizzledClasses;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        swizzledClasses = [NSMutableSet new];
    });
    @synchronized(swizzledClasses) {
        block(swizzledClasses);
    }
}複製代碼

_aspect_modifySwizzledClasses方法裏面保證了swizzledClasses這個Set集合是全局惟一的,而且給傳入的block加上了線程鎖@synchronized( ),保證了block調用中線程是安全的。ui

關於調用aspect_swizzleForwardInvocation,將原IMP指向forwardInvocation是下個階段的事情,咱們先把hookClass看完。this

// 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);複製代碼

當className沒有包含@"_Aspects_"後綴,而且也不是元類,也不是KVO的中間類,即statedClass = = baseClass 的狀況,因而,默認的新建一個子類subclass。

到此,咱們能夠了解到Aspects的設計思想,hook 是在runtime中動態建立子類的基礎上實現的。全部的 swizzling 操做都發生在子類,這樣作的好處是你不須要去更改對象自己的類,也就是,當你在 remove aspects 的時候,若是發現當前對象的 aspect 都被移除了,那麼,你能夠將 isa 指針從新指回對象自己的類,從而消除了該對象的 swizzling ,同時也不會影響到其餘該類的不一樣對象)這樣對原來替換的類或者對象沒有任何影響並且能夠在子類基礎上新增或者刪除aspect。

新建的類的名字,會先加上AspectsSubclassSuffix後綴,即在className後面加上@"_Aspects_",標記成子類。再調用objc_getClass方法,建立這個子類。

/*********************************************************************** * objc_getClass. Return the id of the named class. If the class does * not exist, call _objc_classLoader and then objc_classHandler, either of * which may create a new class. * Warning: doesn't work if aClassName is the name of a posed-for class's isa! **********************************************************************/
Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}複製代碼

objc_getClass會調用look_up_class方法。

/*********************************************************************** * look_up_class * Look up a class by name, and realize it. * Locking: acquires runtimeLock **********************************************************************/
Class look_up_class(const char *name, 
              bool includeUnconnected __attribute__((unused)), 
              bool includeClassHandler __attribute__((unused)))
{
    if (!name) return nil;

    Class result;
    bool unrealized;
    {
        rwlock_reader_t lock(runtimeLock);
        result = getClass(name);
        unrealized = result  &&  !result->isRealized();
    }
    if (unrealized) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(result);
    }
    return result;
}複製代碼

這個方法會去查看有沒有實現叫name的class,查看過程當中會用到rwlock_reader_t lock(runtimeLock),讀寫鎖,底層是用pthread_rwlock_t實現的。

因爲是咱們剛剛新建的一個子類名,頗有多是objc_getClass()返回nil。那麼咱們須要新建這個子類。調用objc_allocateClassPair()方法。

/*********************************************************************** * objc_allocateClassPair * fixme * Locking: acquires runtimeLock **********************************************************************/
Class objc_allocateClassPair(Class superclass, const char *name, 
                             size_t extraBytes)
{
    Class cls, meta;

    rwlock_writer_t lock(runtimeLock);

    // Fail if the class name is in use.
    // Fail if the superclass isn't kosher.
    if (getClass(name)  ||  !verifySuperclass(superclass, true/*rootOK*/)) {
        return nil;
    }

    // Allocate new classes.
    cls  = alloc_class_for_subclass(superclass, extraBytes);
    meta = alloc_class_for_subclass(superclass, extraBytes);

    // fixme mangle the name if it looks swift-y?
    objc_initializeClassPair_internal(superclass, name, cls, meta);

    return cls;
}複製代碼

調用objc_allocateClassPair會新建一個子類,它的父類是入參superclass。

若是新建的子類subclass = = nil,就會報錯,objc_allocateClassPair failed to allocate class。

aspect_swizzleForwardInvocation(subclass)這是下一階段的事情,主要做用是替換當前類forwardInvocation方法的實現爲__ASPECTS_ARE_BEING_CALLED__,先略過。

接着調用aspect_hookedGetClass( ) 方法。

static void aspect_hookedGetClass(Class class, Class statedClass) {
    NSCParameterAssert(class);
    NSCParameterAssert(statedClass);
 Method method = class_getInstanceMethod(class, @selector(class));
 IMP newIMP = imp_implementationWithBlock(^(id self) {
  return statedClass;
 });
 class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
}複製代碼

aspect_hookedGetClass方法是把class的實例方法替換成返回statedClass,也就是說把調用class時候的isa指向了statedClass了。

aspect_hookedGetClass(subclass, statedClass);
  aspect_hookedGetClass(object_getClass(subclass), statedClass);複製代碼

這兩句的意圖咱們也就明白了。

第一句是把subclass的isa指向了statedClass,第二句是把subclass的元類的isa,也指向了statedClass。

最後調用objc_registerClassPair( ) 註冊剛剛新建的子類subclass,再調用object_setClass(self, subclass);把當前self的isa指向子類subclass。

至此,hookClass階段就完成了,成功的把self hook成了其子類 xxx_Aspects_。

2. ASPECTS_ARE_BEING_CALLED

在上一階段hookClass的時候,有幾處都調用了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));
}複製代碼

aspect_swizzleForwardInvocation就是整個Aspects hook方法的開始。

/*********************************************************************** * class_replaceMethod **********************************************************************/
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;

    return _class_addMethod(cls, name, imp, types, YES);
}複製代碼

調用class_replaceMethod方法,實際底層實現是調用_class_addMethod方法。

static IMP _class_addMethod(Class cls, SEL name, IMP imp, 
                            const char *types, bool replace)
{
    old_method *m;
    IMP result = nil;

    if (!types) types = "";

    mutex_locker_t lock(methodListLock);

    if ((m = _findMethodInClass(cls, name))) {
        // already exists
        // fixme atomic
        result = method_getImplementation((Method)m);
        if (replace) {
            method_setImplementation((Method)m, imp);
        }
    } else {
        // fixme could be faster
        old_method_list *mlist = 
            (old_method_list *)calloc(sizeof(old_method_list), 1);
        mlist->obsolete = fixed_up_method_list;
        mlist->method_count = 1;
        mlist->method_list[0].method_name = name;
        mlist->method_list[0].method_types = strdup(types);
        if (!ignoreSelector(name)) {
            mlist->method_list[0].method_imp = imp;
        } else {
            mlist->method_list[0].method_imp = (IMP)&_objc_ignored_method;
        }

        _objc_insertMethods(cls, mlist, nil);
        if (!(cls->info & CLS_CONSTRUCTING)) {
            flush_caches(cls, NO);
        } else {
            // in-construction class has no subclasses
            flush_cache(cls);
        }
        result = nil;
    }

    return result;
}複製代碼

從上述源碼中,咱們能夠看到,先_findMethodInClass(cls, name),從cls中查找有沒有name的方法。若是有,而且能找到對應的IMP的話,就進行替換method_setImplementation((Method)m, imp),把name方法的IMP替換成imp。這種方式_class_addMethod返回的是name方法對應的IMP,實際上就是咱們替換完的imp。

若是在cls中沒有找到name方法,那麼就添加該方法,在mlist -> method_list[0] 的位置插入新的name方法,對應的IMP就是傳入的imp。這種方式_class_addMethod返回的是nil。

回到aspect_swizzleForwardInvocation中,

IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
   class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}複製代碼

把forwardInvocation:的IMP替換成__ASPECTS_ARE_BEING_CALLED__ 。若是在klass裏面找不到forwardInvocation:方法,就會新添加該方法。

因爲子類自己並無實現 forwardInvocation ,隱藏返回的 originalImplementation 將爲空值,因此也不會生成 NSSelectorFromString(AspectsForwardInvocationSelectorName) 。因此還須要_class_addMethod會爲咱們添加了forwardInvocation:方法的實現

若是originalImplementation返回的不是nil,就說明已經替換成功。替換完方法以後,咱們在klass中再加入一個叫「__aspects_forwardInvocation:」的方法,對應的實現也是(IMP)__ASPECTS_ARE_BEING_CALLED__。

接下來就是整個Aspects的核心實現了:__ASPECTS_ARE_BEING_CALLED__

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];
    NSLog(@"%@",info.arguments);
    NSArray *aspectsToRemove = nil;

    …… ……
}複製代碼

這一段是hook前的準備工做:

  1. 獲取原始的selector
  2. 獲取帶有aspects_xxxx前綴的方法
  3. 替換selector
  4. 獲取實例對象的容器objectContainer,這裏是以前aspect_add關聯過的對象。
  5. 獲取得到類對象容器classContainer
  6. 初始化AspectInfo,傳入self、invocation參數
// Before hooks.
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);複製代碼

調用宏定義執行Aspects切片功能

#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
    [aspect invokeWithInfo:info];\
    if (aspect.options & AspectOptionAutomaticRemoval) { \
        aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
    } \
}複製代碼

之因此這裏用一個宏定義來實現裏面的功能,是爲了得到一個更加清晰的堆棧信息。

宏定義裏面就作了兩件事情,一個是執行了[aspect invokeWithInfo:info]方法,一個是把須要remove的Aspects加入等待被移除的數組中。

[aspect invokeWithInfo:info]方法在上篇裏面詳細分析過了其實現,這個函數的主要目的是把blockSignature初始化blockSignature獲得invocation。而後處理參數,若是參數block中的參數大於1個,則把傳入的AspectInfo放入blockInvocation中。而後從originalInvocation中取出參數給blockInvocation賦值。最後調用[blockInvocation invokeWithTarget:self.block];這裏Target設置爲self.block。也就執行了咱們hook方法的block。

因此只要調用aspect_invoke(classContainer.Aspects, info);這個核心替換的方法,就能hook咱們原有的SEL。對應的,函數第一個參數分別傳入的是classContainer.beforeAspects、classContainer.insteadAspects、classContainer.afterAspects就能對應的實現before、instead、after對應時間的Aspects切片的hook。

// 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)));
    }複製代碼

這一段代碼是實現Instead hooks的。先判斷當前insteadAspects是否有數據,若是沒有數據則判斷當前繼承鏈是否能響應aspects_xxx方法,若是能,則直接調用aliasSelector。注意:這裏的aliasSelector是原方法method

// After hooks.
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);複製代碼

這兩行是對應的執行After hooks的。原理如上。

至此,before、instead、after對應時間的Aspects切片的hook若是能被執行的,都執行完畢了。

若是hook沒有被正常執行,那麼就應該執行原來的方法。

// 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];
        }
    }複製代碼

invocation.selector先換回原來的originalSelector,若是沒有被hook成功,那麼AspectsForwardInvocationSelectorName還能再拿到原來的IMP對應的SEL。若是能相應,就調用原來的SEL,不然就報出doesNotRecognizeSelector的錯誤。

[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];複製代碼

最後調用移除方法,移除hook。

3. prepareClassAndHookSelector

如今又要回到上篇中提到的aspect_prepareClassAndHookSelector方法中來了。

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));
    }
}複製代碼

klass是咱們hook完原始的class以後獲得的子類,名字是帶有_Aspects_後綴的子類。由於它是當前類的子類,因此也能夠從它這裏獲取到原有的selector的IMP。

static BOOL aspect_isMsgForwardIMP(IMP impl) {
    return impl == _objc_msgForward
#if !defined(__arm64__)
    || impl == (IMP)_objc_msgForward_stret
#endif
    ;
}複製代碼

這裏是判斷當前IMP是否是_objc_msgForward或者_objc_msgForward_stret,即判斷當前IMP是否是消息轉發。

若是不是消息轉發,就先獲取當前原始的selector對應的IMP的方法編碼typeEncoding。

若是子類裏面不能響應aspects_xxxx,就爲klass添加aspects_xxxx方法,方法的實現爲原生方法的實現。

Aspects整個hook的入口就是這句話:

class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);複製代碼

因爲咱們將slector指向_objc_msgForward 和_objc_msgForward_stret,可想而知,當selector被執行的時候,也會觸發消息轉發從而進入forwardInvocation,而咱們又對forwardInvacation進行了swizzling,所以,最終轉入咱們本身的處理邏輯代碼中。

4. aspect_remove

aspect_remove整個銷燬過程的函數調用棧

- aspect_remove(AspectIdentifier *aspect, NSError **error)
  └── aspect_cleanupHookedClassAndSelector
      ├──aspect_deregisterTrackedSelector
      │   └── aspect_getSwizzledClassesDict
      ├──aspect_destroyContainerForObject
      └── aspect_undoSwizzleClassInPlace
          └── _aspect_modifySwizzledClasses
                └──aspect_undoSwizzleForwardInvocation複製代碼
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];

            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;
}複製代碼

aspect_remove 是整個 aspect_add的逆過程。
aspect_performLocked是保證線程安全。把AspectsContainer都置爲空,remove最關鍵的過程就是aspect_cleanupHookedClassAndSelector(self, aspect.selector);移除以前hook的class和selector。

static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);

    Class klass = object_getClass(self);
    BOOL isMetaClass = class_isMetaClass(klass);
    if (isMetaClass) {
        klass = (Class)self;
    }

    ……  ……
}複製代碼

klass是如今的class,若是是元類,就轉換成元類。

// 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));
    }複製代碼

先回復MsgForward消息轉發函數,得到方法簽名,而後把原始轉發方法替換回咱們hook過的方法。

這裏有一個須要注意的問題。

若是當前Student有2個實例,stu1和stu2,而且他們都同時hook了相同的方法study( ),stu2在執行完aspect_remove,把stu2的study( )方法還原了。這裏會把stu1的study( )方法也還原了。由於remove方法這個操做是對整個類的全部實例都生效的。

要想每一個實例還原各自的方法,不影響其餘實例,上述這段代碼刪除便可。由於在執行 remove 操做的時候,其實和這個對象相關的數據結構都已經被清除了,即便不去恢復 stu2 的study( ) 的執行,在進入 __ASPECTS_ARE_BEING_CALLED__,因爲這個沒有響應的 aspects ,其實會直接跳到原來的處理邏輯,並不會有其餘附加影響。

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];
    AspectTracker *subclassTracker = nil;
    do {
        AspectTracker *tracker = swizzledClassesDict[currentClass];
        if (subclassTracker) {
            [tracker removeSubclassTracker:subclassTracker hookingSelectorName:selectorName];
        } else {
            [tracker.selectorNames removeObject:selectorName];
        }
        if (tracker.selectorNames.count == 0 && tracker.selectorNamesToSubclassTrackers) {
            [swizzledClassesDict removeObjectForKey:currentClass];
        }
        subclassTracker = tracker;
    }while ((currentClass = class_getSuperclass(currentClass)));
}複製代碼

還要移除AspectTracker裏面全部標記的swizzledClassesDict。銷燬所有記錄的selector。

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);
            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);
            }else if (self.class != klass) {
                aspect_undoSwizzleClassInPlace(klass);
            }
        }
    }複製代碼

最後,咱們還須要還原類的AssociatedObject關聯對象,以及用到的AspectsContainer容器。

static void aspect_destroyContainerForObject(id<NSObject> self, SEL selector) {
    NSCParameterAssert(self);
    SEL aliasSelector = aspect_aliasForSelector(selector);
    objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);
}複製代碼

這個方法銷燬了AspectsContainer容器,而且把關聯對象也置成了nil。

static void aspect_undoSwizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    NSString *className = NSStringFromClass(klass);

    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        if ([swizzledClasses containsObject:className]) {
            aspect_undoSwizzleForwardInvocation(klass);
            [swizzledClasses removeObject:className];
        }
    });
}複製代碼

aspect_undoSwizzleClassInPlace會再調用aspect_undoSwizzleForwardInvocation方法。

static void aspect_undoSwizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName));
    Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
    // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy.
    IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
    class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");

    AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass));
}複製代碼

最後還原ForwardInvocation的Swizzling,把原來的ForwardInvocation再交換回來。

六. 關於Aspects 的一些 「坑」

在Aspects這個庫了,用到了Method Swizzling有幾處,這幾處若是處理很差,就會掉「坑」裏了。

1.aspect_prepareClassAndHookSelector 中可能遇到的「坑」

在aspect_prepareClassAndHookSelector方法中,會把原始的selector hook成_objc_msgForward。可是若是這裏的selector就是_objc_msgForward會發生什麼呢?

其實這裏的坑在做者的代碼註釋裏面已經隱藏的提到了。

在__ASPECTS_ARE_BEING_CALLED__方法中,最後轉發消息的那段代碼裏面有這樣一段註釋

// If no hooks are installed, call original implementation (usually to throw an exception)複製代碼

看到這段註釋之後,你確定會思考,爲什麼到了這裏就會throw an exception呢?緣由是由於找不到NSSelectorFromString(AspectsForwardInvocationSelectorName)對應的IMP。

再往上找,就能夠找到緣由了。在實現aspect_prepareClassAndHookSelector中,會判斷當前的selector是否是_objc_msgForward,若是不是msgForward,接下來什麼也不會作。那麼aliasSelector是沒有對應的實現的。

因爲 forwardInvocation 被 aspects 所 hook ,最終會進入到 aspects 的處理邏輯__ASPECTS_ARE_BEING_CALLED__中來,此時若是沒有找不到 aliasSelector 的 IMP 實現,所以會在此進行消息轉發。並且子類並無實現 NSSelectorFromString(AspectsForwardInvocationSelectorName),因而轉發就會拋出異常。

這裏的「坑」就在於,hook的selector若是變成了_objc_msgForward,就會出現異常了,可是通常咱們不會去hook _objc_msgForward這個方法,出現這個問題的緣由是有其餘的Swizzling會去hook這個方法。

好比說JSPatch把傳入的 selector 先被 JSPatch hook ,那麼,這裏咱們將不會再處理,也就不會生成 aliasSelector 。就會出現閃退的異常了。

static Class aspect_hookClass(NSObject *self, NSError **error) {
    ...
    subclass = objc_allocateClassPair(baseClass, subclassName, 0);
    ...
    IMP originalImplementation = class_replaceMethod(subclass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    if (originalImplementation) {
        class_addMethod(subclass, NSSelectorFromString(AspectsForwardInvocationSelectorName),   originalImplementation, "v@:@");
    } else {
        Method baseTargetMethod = class_getInstanceMethod(baseClass, @selector(forwardInvocation:));
        IMP baseTargetMethodIMP = method_getImplementation(baseTargetMethod);
        if (baseTargetMethodIMP) {
            class_addMethod(subclass, NSSelectorFromString(AspectsForwardInvocationSelectorName), baseTargetMethodIMP, "v@:@");
        }
    }
    ...
}複製代碼

這裏在這篇文章中給出了一個解決辦法:

在對子類的 forwardInvocation方法進行交換而不只僅是替換,實現邏輯以下,強制生成一個 NSSelectorFromString(AspectsForwardInvocationSelectorName)指向原對象的 forwardInvocation的實現。

注意若是 originalImplementation爲空,那麼生成的 NSSelectorFromString(AspectsForwardInvocationSelectorName)
將指向 baseClass 也就是真正的這個對象的 forwradInvocation ,這個其實也就是 JSPatch hook 的方法。同時爲了保證 block 的執行順序(也就是前面介紹的 before hooks / instead hooks / after hooks ),這裏須要將這段代碼提早到 after hooks 執行以前進行。這樣就解決了 forwardInvocation 在外面已經被 hook 以後的衝突問題。

2. aspect_hookSelector 可能出現的 「坑」

在Aspects中主要是hook selector,此時若是有多個地方會和Aspects去hook相同方法,那麼也會出現doesNotRecognizeSelector的問題。

舉個例子,好比說在NSArray中用Aspects 去hook了objectAtIndex的方法,而後在NSMutableArray中Swizzling了objectAtIndex方法。在
NSMutableArray中,調用objectAtIndex就有可能出錯。

由於仍是在於Aspects hook 了selector以後,會把原來的selector變成_objc_msgForward。等到NSMutableArray再去hook這個方法的時候,記錄的是IMP就是_objc_msgForward這個了。若是這時objc_msgSend執行原有實現,就會出錯了。由於原有實現已經被替換爲_objc_msgForward,而真的IMP因爲被Aspects先Swizzling掉了,因此找不到。

解決辦法仍是相似JSPatch的解決辦法:

把-forwardInvocation:也進行Swizzling,在本身的-forwardInvocation:方法中進行一樣的操做,就是判斷傳入的NSInvocation的Selector,被Swizzling的方法指向了_objc_msgForward(或_objc_msgForward_stret)若是是本身能夠識別的Selector,那麼就將Selector變爲原有Selector在執行,若是不識別,就直接轉發。

最後

最後用一張圖總結一下Aspects總體流程:

請你們多多指教。

相關文章
相關標籤/搜索