(Next)

Detailed explanation of Aspects Hook process

Let’s take a look at the function call stack

- aspect_prepareClassAndHookSelector(self, selector, error); ├ ─ ─ aspect_hookClass (self, The error) │ ├ ─ ─ aspect_swizzleClassInPlace │ ├ ─ ─ aspect_swizzleForwardInvocation │ │ └ ─ ─ __ASPECTS_ARE_BEING_CALLED__ │ │ ├── ├─ ├─ ├─ ├─ aspect_remove │ ├─ ├─ ├─ Aspect_remove │ ├─ ├─ ├─ ├─ ├─ aspect_get guitar Class ├─ aspect_get guitar class exercisesCopy the code

Can be seen from the call stack, Aspects hook process mainly four stages, hookClass, ASPECTS_ARE_BEING_CALLED, prepareClassAndHookSelector, remove.

1. hookClass


 NSCParameterAssert(self);
 Class statedClass = self.class;
 Class baseClass = object_getClass(self);
 NSString *className = NSStringFromClass(baseClass);Copy the code

StatedClass is different from baseClass.


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

+ (Class)class {
    return self;
}Copy the code

StatedClass is the fetching class object, baseClass is the fetching class 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); }Copy the code

To determine whether is used for the className include hasSuffix: AspectsSubclassSuffix



static NSString *const AspectsSubclassSuffix = @"_Aspects_";Copy the code

If the suffix @”_Aspects_” is included, the class is already hooked, and return. If it does not contain the @”_Aspects_” suffix, then determine whether it is baseClass or metaclass. If it is, call aspect_swizzleClassInPlace. If it is not a metaclass, then check whether statedClass and baseClass are equal. If they are not equal, the object is KVO, because the isa pointer to the KVO object points to an intermediate class. Call aspect_swizzleClassInPlace on the KVO intermediate class.


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;
}Copy the code

_aspect_modifySwizzledClasses passes a block with an entry parameter (NSMutableSet *swizzledClasses) that determines whether the Set contains the current ClassName. If not, The aspect_swizzleForwardInvocation() method is called and the className is added to the Set invocation.


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); }}Copy the code

The _aspect_modifySwizzledClasses method ensures that the Set of swizzledClasses is globally unique, and adds a thread lock @synchronized() to the block that is passed in to ensure that the block calls are thread-safe.

Pointing the IMP to the forwardInvocation is the next step in terms of calling the aspect_swizzleForwardInvocation, so let’s finish with the hookClass.


// 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);Copy the code

When className does not contain the @”_Aspects_” suffix and is not a metaclass or intermediate class of KVO, statedClass = = baseClass, a new subclass is created by default.

At this point, we can see how Aspects are designed. Hooks are implemented on the basis of dynamically subclassing the Runtime. All swizzling takes place in the subclass. The advantage of this is that you don’t need to change the class of the object itself. That is, if you find that the aspect of the current object has been removed during remove Aspects, then you can repoint the ISA pointer back to the class of the object itself. This eliminates the swizzling of the object and does not affect the different objects of the other class.) This has no effect on the originally replaced class or object and can add or remove aspects from subclasses.

New class names are subclassed with AspectsSubclassSuffix (@”_Aspects_” followed by className). Create this subclass by calling 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);
}Copy the code

Objc_getClass calls the look_up_class method.


/***********************************************************************
* 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;
    boolunrealized; { rwlock_reader_t lock(runtimeLock); result = getClass(name); unrealized = result && ! result->isRealized(); }if (unrealized) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(result);
    }
    return result;
}Copy the code

Rwlock_reader_t lock(runtimeLock); pthread_rwlock_t;

Since it’s a new subclass name we just created, it’s likely that objc_getClass() returns nil. So we need to create this new subclass. Call the objc_allocateClassPair() method.


/***********************************************************************
* 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;
}Copy the code

Calling objc_allocateClassPair creates a new subclass whose parent is the input parameter superclass.

Objc_allocateClassPair failed to allocate class if subclass = = nil is created.

Aspect_swizzleForwardInvocation (subclass) This is the next phase and will replace the current class forwardInvocation method implementation to __ASPECTS_ARE_BEING_CALLED__, which is skipped.

The aspect_hookedGetClass() method is then called.



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));
}Copy the code

The aspect_hookedGetClass method replaces the instance method of the class with the one that returns statedClass.


  aspect_hookedGetClass(subclass, statedClass);
  aspect_hookedGetClass(object_getClass(subclass), statedClass);Copy the code

We understand the meaning of these two sentences.

StatedClass isa = statedClass isa = statedClass isa = statedClass isa = statedClass isa = statedClass isa

Object_setClass (self, subclass); Redirect the current SELF isa to subclass.

At this point, the hookClass phase is complete, successfully subclassing self hook as xxx_Aspects_.

2. ASPECTS_ARE_BEING_CALLED

In the previous hookClass phase, the aspect_swizzleForwardInvocation was called at several points.


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));
}Copy the code

The aspect_swizzleForwardInvocation was the beginning of the whole Aspects hook method.


/*********************************************************************** * 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);
}Copy the code

Call the class_replaceMethod method. The actual underlying implementation calls the _class_addMethod method.


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;
}Copy the code

_findMethodInClass(CLS, name); Replace method_setImplementation((Method)m, IMP) with IMP if there is one, and the corresponding IMP is found. In this way, _class_addMethod returns the IMP corresponding to the name method, which is actually the IMP we replaced.

If the name method is not found in the CLS, add it and insert a new name method at mlist -> method_list[0], corresponding to the IMP passed in. This way _class_addMethod returns nil.

Back to the aspect_swizzleForwardInvocation,


IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
   class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}Copy the code

Replace the IMP from forwardInvocation with __ASPECTS_ARE_BEING_CALLED__. If the forwardInvocation: method is not found in klass, it is added.

Since the subclass does not implement the forwardInvocation itself, the originalImplementation returned by hiding will be null, So won’t generate NSSelectorFromString (AspectsForwardInvocationSelectorName). So the _class_addMethod requirement will add the forwardInvocation method for us

If originalImplementation does not return nil, then the replacement has been successful. After the method replacement, we added a klass method called “__aspects_forwardInvocation:” which is also implemented (IMP)__ASPECTS_ARE_BEING_CALLED__.

Next comes the core implementation of the entire 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; ... ... }Copy the code

This paragraph is the preparation before hook:

  1. Get the original selector
  2. Gets a method prefixed with aspects_xxxx
  3. Replace the selector
  4. Get the instance object’s container, objectContainer, where aspect_Add was previously associated.
  5. Get Gets the class object container classContainer
  6. Initialize AspectInfo, passing in the self and Invocation parameters

    // Before hooks.
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);Copy the code

Call macros to define how to perform Aspects slicing


#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect inaspects) {\ [aspect invokeWithInfo:info]; \if(aspect.options & AspectOptionAutomaticRemoval) { \ aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect];  \} \}Copy the code

The purpose of using a macro definition here is to get a clearer view of the stack.

The macro definition does two things, one is to implement the [aspect invokeWithInfo:info] method, and the other is to add the Aspects that need to be removed to the array waiting to be removed.

The [aspect invokeWithInfo:info] method was analyzed in detail in the previous article. The main purpose of this function is to initialize blockSignature to the blockSignature invocation. The arguments are then processed, and if the argument block has more than one argument, the incoming AspectInfo is put into the Block Invocation. Then extract the parameter from the originalInvocation to the blockInvocation. Last call [blockInvocation invokeWithTarget: self. Block]; Here Target is set to self.block. This executes the block of our hook method.

So just call aspect_invoke(classContainer.Aspects, info); This core replacement method can hook our original SEL. The corresponding, Function first parameter respectively incoming is classContainer beforeAspects, classContainer. InsteadAspects, classContainer afterAspects can the realization of the corresponding before, home Hook of corresponding time Aspects section.


    // 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))); }Copy the code

This code implements Instead hooks. Determine whether the current insteadAspects has data. If not, determine whether the current inheritance chain can respond to the Aspects_xxx method. If so, call aliasSelector directly. Note that the aliasSelector here is the original method


    // After hooks.
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);Copy the code

These two lines correspond to executing After hooks. The principle is described above.

At this point, all hooks of before, instead and after Aspects slices at corresponding time, if they can be executed, have been executed.

If the hook is not executed properly, then the original method should be executed.


    // 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{[selfdoesNotRecognizeSelector:invocation.selector]; }}Copy the code

Invocation. The selector switch back to the original originalSelector first, if not be hook is successful, then AspectsForwardInvocationSelectorName can also get the original IMP corresponding SEL. If corresponding, call original SEL, otherwise doesNotRecognizeSelector error is reported.


[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];Copy the code

Finally, call the remove method to remove the hook.

3. prepareClassAndHookSelector

Now again, I will go back to the aspect_prepareClassAndHookSelector method mentioned in the previous Posting here.


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]) { __unusedBOOL 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)); }}Copy the code

Klass is a subclass whose name has the _Aspects_ suffix after we hook the original class. Because it’s a subclass of the current class, you can also get the IMP of the original selector from it.


static BOOL aspect_isMsgForwardIMP(IMP impl) {
    return impl == _objc_msgForward
#if ! defined(__arm64__)
    || impl == (IMP)_objc_msgForward_stret
#endif
    ;
}Copy the code

Whether the current IMP is _objc_msgForward or _objc_msgForward_stret, that is, whether the current IMP is message forwarding.

If it is not message forwarding, typeEncoding is first obtained for the IMP method corresponding to the current original selector.

If the subclass does not respond to aspects_xxxx, add an aspects_xxxx method to klass, which is implemented as the native method.

The entrance to the whole Hook of Aspects is this sentence:


class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);Copy the code

Since we have the slector pointing to the _objc_msgForward and _objc_msgForward_stret, you can imagine that when the selector is executed, it will also fire the message forward into the forwardInvocation, We swizzling forwardInvacation, so we end up in our own processing logic code.

4. aspect_remove

Aspect_remove the function call stack for the entire destruction process

- aspect_remove(AspectIdentifier *aspect, NSError * * error) └ ─ ─ aspect_cleanupHookedClassAndSelector ├ ─ ─ aspect_deregisterTrackedSelector │ └ ─ ─ Aspect_getSwizzledClassesDict ├ ─ ─ aspect_destroyContainerForObject └ ─ ─ aspect_undoSwizzleClassInPlace └ ─ ─ _aspect_modifySwizzledClasses └ ─ ─ aspect_undoSwizzleForwardInvocationCopy the code

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;
}Copy the code

Aspect_remove is the reverse of aspect_Add. Aspect_performLocked keeps threads safe. Put AspectsContainer are empty, remove the key process is aspect_cleanupHookedClassAndSelector (self, aspect. The selector); Remove the class and selector of the previous hook.



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; }... ... }Copy the code

Klass is the current class, and if it’s a metaclass, it’s converted to a metaclass.


    // 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));
    }Copy the code

First reply to MsgForward message forwarding function, get method signature, then replace original forwarding method back to our hook method.

There is a caveat here.

If Student has two instances, stu1 and stu2, and they both hook the same study() method at the same time, stu2 restores stu2’s study() method after executing aspect_remove. Stu1’s study() method is also restored here. The remove method applies to all instances of the entire class.

To restore the method for each instance without affecting other instances, delete the code above. Since all data structures associated with this object are removed at the time of the remove operation, the __ASPECTS_ARE_BEING_CALLED__, without restoring the execution of stu2’s study(), goes into __ASPECTS_ARE_BEING_CALLED__ because of the unresponsive aspects, In fact, it jumps directly to the original processing logic, with no additional impact.


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)));
}Copy the code

Also remove swizzledClassesDict for all tags in AspectTracker. Destroys all records of the 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); }}}Copy the code

Finally, we need to restore the AssociatedObject AssociatedObject of the class and the AspectsContainer used.


static void aspect_destroyContainerForObject(id<NSObject> self, SEL selector) {
    NSCParameterAssert(self);
    SEL aliasSelector = aspect_aliasForSelector(selector);
    objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);
}Copy the code

This method destroys the AspectsContainer and sets the associated object to 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]; }}); }Copy the code

Will call again aspect_undoSwizzleClassInPlace aspect_undoSwizzleForwardInvocation method.


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));
}Copy the code

The ForwardInvocation Swizzling is restored and the ForwardInvocation is exchanged back.

Vi. Some “pits” about Aspects

In the Aspects library, Method Swizzling is used in several places, which will fall into “pits” if not handled properly.

1. That may be met in aspect_prepareClassAndHookSelector “pit”

In aspect_prepareClassAndHookSelector approach, the original selector that hooks into _objc_msgForward. But what happens if the selector here is _objc_msgForward?

In fact, this hole is hidden in the author’s code comments have been mentioned.

In the __ASPECTS_ARE_BEING_CALLED__ method, there is a comment in the last section of code that forwards the message


// If no hooks are installed, call original implementation (usually to throw an exception)Copy the code

Why throw an exception? Because can’t find NSSelectorFromString (AspectsForwardInvocationSelectorName) corresponding to the IMP.

If you look higher, you’ll find out why. In realizing aspect_prepareClassAndHookSelector, will figure out whether the current selector _objc_msgForward, if not msgForward, nothing will do next. So there’s no implementation of aliasSelector.

Since the forwardInvocation is hooked by aspects, it ends up in the aspects processing logic __ASPECTS_ARE_BEING_CALLED__, If there is no IMP implementation that cannot find aliasSelector at this point, the message is forwarded here. And the subclass has not fulfilled NSSelectorFromString (AspectsForwardInvocationSelectorName), then forward it throws an exception.

The trick here is that if the selector of a hook changes to _objc_msgForward, it will get an exception, but we usually don’t hook the method _objc_msgForward, The reason for this problem is that there are other Swizzling who hook this method.

Let’s say JSPatch takes the incoming selector and JSPatch hooks it first, so we’re not going to do anything about it here, and we’re not going to generate an aliasSelector. There will be abnormal flash back.


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@:@"); }}... }Copy the code

Here’s a solution in this article:

When the forwardInvocation method of the subclass is exchanged instead of just replaced, the logic is as follows, Forced to generate a NSSelectorFromString (AspectsForwardInvocationSelectorName) refer to the original object forwardInvocation implementation.

Notice if originalImplementation is empty, Then generated NSSelectorFromString (AspectsForwardInvocationSelectorName) will point to baseClass is true this object forwradInvocation, this also is actually JSPatch Hook method. Also, in order to keep the order of execution of blocks (i.e., before hooks/instead hooks/After hooks described earlier), this code needs to be moved up to before after hooks are executed. This solves the forwardInvocation problem after the invocation is already hooked outside.

2. Aspect_hookSelector possible “pit”

In Aspects, it is mainly hook selector. At this time, if there are many places that hook the same method as Aspects, doesnot trecognizeselector will also appear.

For example, in NSArray we hook objectAtIndex methods with Aspects, and then Swizzling objectAtIndex methods in NSMutableArray. In NSMutableArray, it is possible to get an error by calling objectAtIndex.

Because again, when Aspects hook a selector, they change the original selector to _objc_msgForward. By the time NSMutableArray hooks the method, IMP is _objc_msgForward. If objc_msgSend executes the original implementation at this point, an error will occur. Since the original implementation has been replaced with _objc_msgForward, the real IMP cannot be found because Aspects have Swizzling removed it.

The solution is again a Jspatch-like solution:

The -forwardInvocation: Swizzling is the same in your own -Forward Invocation: method, which determines the incoming NSInvocation Selector, Swizzling’s method points to _objc_msgForward (or _objc_msgForward_stret) if it is a Selector it can recognize, then the Selector is executed as the original Selector, and if it is not recognized, it is forwarded directly.

The last

Finally, summarize the overall Aspects process with a diagram:

Please give us more advice.