Detailed explanation of the aspects framework of IOS

Time:2020-1-14

We must put forward such tasks to ourselves: first, learning, second, and third, learning. ——Lenin

I saw a live sharing of aspects today, so I want to write an article to share aspects with you

1: What are aspects?

Aspects is an open source library for aspect oriented programming, which allows you to add any code to every class and method existing in every instance. You can execute before or after the method is executed, or you can replace the original method. Hook is implemented through runtime message forwarding. Aspects will automatically handle superclasses, which is easier to use than normal method calls. The star on GitHub has exceeded 6000, which is relatively stable;

Start with the source code, and then summarize it. If you are not interested in the source code, you can directly jump to the end of the article to view the specific process

Aspects is the preparation before hook

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

Add Hook through the above method, import SEL (to Hook method), options (before or after the call to the remote method invocation or replacement), block (to execute the code), error (error message)

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

    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        //First, judge the validity of the parameter. If not, return to nil directly
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            //Parameter method
            //Create container
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            //Create an aspectidentifier object (save hook content)
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                //Add the identifier to the container (according to options, to different collections)
                [aspectContainer addAspect:identifier withOptions:options];

                // Modify the class to allow message interception.
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}

The above method is mainly divided into the following steps:

  • Judge the validity of the method passed in above
  • If the aspectscontainer container class is created legally, the container will be classified according to the incoming slicing time and added to the corresponding collection
  • Create aspectidentifier object to save hook content
  • If the aspectidentifier object is created successfully, add the aspectidentifier to the corresponding array according to options
  • Finally, it calls aspect [prepareclassandhookselector (self, selector, error); it starts hook

Next, read the above steps one by one

1: Judge the validity of the incoming method

/*
 Judge the validity of parameters:
 1. first add retain, release, autorelease, forwardInvocation to the array. If the SEL is one of the arrays, an error is reported
And return no. These are all methods that can't be swizzled
 2. Whether the incoming time is correct, and whether sel is dealloc. If it is dealloc, the selected calling time must be aspectpositionbefore
 3. Judge whether the class or class object responds to the incoming sel
 4. If the class method is replaced, check whether to replace it repeatedly
 */
static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
    static NSSet *disallowedSelectorList;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
    });

    // Check against the blacklist.
    NSString *selectorName = NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }

    // Additional checks.
    AspectOptions position = options&AspectPositionFilter;
    //If it is dealloc, it must be aspectpositionbefore, otherwise an error will be reported
    if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
        NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
        AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
        return NO;
    }
    //Judge whether a method can be responded, respondstoselector (judge whether an object responds to a method), instancesrespondtoselector (judge whether a class can respond to a method)
    if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
        NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
        AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
        return NO;
    }

    // Search for the current class and the class hierarchy IF we are modifying a class object
    //Determine whether it is a metaclass,
    if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        //Create dictionary
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];
        do {
            AspectTracker *tracker = swizzledClassesDict[currentClass];
            if ([tracker.selectorNames containsObject:selectorName]) {

                // Find the topmost class for the log.
                if (tracker.parentEntry) {
                    AspectTracker *topmostEntry = tracker.parentEntry;
                    while (topmostEntry.parentEntry) {
                        topmostEntry = topmostEntry.parentEntry;
                    }
                    NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];
                    AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
                    return NO;
                }else if (klass == currentClass) {
                    // Already modified and topmost!
                    return YES;
                }
            }
        }while ((currentClass = class_getSuperclass(currentClass)));

        // Add the selector as being modified.
//This means that the parameter passed in is legal and has not been hook, so the information can be saved
        currentClass = klass;
        AspectTracker *parentTracker = nil;
        do {
            AspectTracker *tracker = swizzledClassesDict[currentClass];
            if (!tracker) {
                tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
                swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
            }
            [tracker.selectorNames addObject:selectorName];
            // All superclasses get marked as having a subclass that is modified.
            parentTracker = tracker;
        }while ((currentClass = class_getSuperclass(currentClass)));
    }

    return YES;
}

The above code mainly does the following:

  • Add “retain”, “release”, “autorelease”, “forwardinvocation: to the collection to determine whether the collection contains the incoming selector. If no is returned, it also means that aspects cannot hook these functions;
  • Judge whether selector is a dealloc method. If the cut section must be AspectPositionBefore, otherwise it will report error and return to NO. After dealloc, the object will be destroyed, so the slice time can only be called before the original method invocation.
  • Judge whether the class and instance object can respond to the incoming selector, and cannot return No
  • Judge whether it is a metaclass. If it is a metaclass, judge whether the method has been hook. If not, save the data. A method can only hook once in a class level

2. Create aspectscontainer container class

// Loads or creates the aspect container.
static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    //Spliced string aspects:
    SEL aliasSelector = aspect_aliasForSelector(selector);
    //Get aspectcontainer object
    AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
    //If not, create
    if (!aspectContainer) {
        aspectContainer = [AspectsContainer new];
        objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
    }
    return aspectContainer;
}

Get its corresponding associatedobject Association object. If not, create an association object. Finally, we get that the selector has the prefix “aspects” and the corresponding aspectcontainer.

3. Create aspectidentifier object to save hook content

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
    NSCParameterAssert(block);
    NSCParameterAssert(selector);
/// convert blcok to method signature
    NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
    //Aspect "iscompatibleblocksignature compares the block to be replaced with the original method. If it is not the same, do not continue
    //If so, assign all parameters to the aspectidentifier object
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }

    AspectIdentifier *identifier = nil;

    if (blockSignature) {
        identifier = [AspectIdentifier new];
        identifier.selector = selector;
        identifier.block = block;
        identifier.blockSignature = blockSignature;
        identifier.options = options;
        identifier.object = object; // weak
    }
    return identifier;
}
/*
 1. Convert the original method to method signature
 2. Then compare the number of parameters signed by the two methods. If they are not equal, they are different
 3. If the number of parameters is the same, then compare the first parameter of blocksignature
 */
static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
    NSCParameterAssert(blockSignature);
    NSCParameterAssert(object);
    NSCParameterAssert(selector);

    BOOL signaturesMatch = YES;
    //Convert the original method to method signature
    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
    //Judge the number of parameters of two method numbers
    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        //If the first parameter of blocksignature is "CMD" and the corresponding type is' @ ', if it is not equal to' @ ', it does not match
        if (blockSignature.numberOfArguments > 1) {
            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
            if (blockType[0] != '@') {
                signaturesMatch = NO;
            }
        }
        // Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2.
        // The block can have less arguments than the method, that's ok.
        //If signaturesmatch = yes, the following is a more rigorous comparison
        if (signaturesMatch) {
            for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
                const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
                const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
                // Only compare parameter, not the optional type data.
                if (!methodType || !blockType || methodType[0] != blockType[0]) {
                    signaturesMatch = NO; break;
                }
            }
        }
    }
    //If the signaturesmatch is no after the above comparison, an exception will be thrown
    if (!signaturesMatch) {
        NSString *description = [NSString stringWithFormat:@"Blog signature %@ doesn't match %@.", blockSignature, methodSignature];
        AspectError(AspectErrorIncompatibleBlockSignature, description);
        return NO;
    }
    return YES;
}
//Convert blcok to method signature
#Pragma mark transforms blcok into method signature
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
    //Determine whether there is an aspectblockflagshassignature flag bit. No error is reported that does not contain method signature
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    void *desc = layout->descriptor;
    desc += 2 * sizeof(unsigned long int);
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
        desc += 2 * sizeof(void *);
    }
    if (!desc) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

This method first converts the block into a method signature, and then compares it with the original method signature. If it does not return no, it will perform the assignment operation

4. Add aspectidentifier to the corresponding array according to options

- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
    NSParameterAssert(aspect);
    NSUInteger position = options&AspectPositionFilter;
    switch (position) {
        case AspectPositionBefore:  self.beforeAspects  = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
        case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
        case AspectPositionAfter:   self.afterAspects   = [(self.afterAspects  ?:@[]) arrayByAddingObject:aspect]; break;
    }
}

According to the incoming slice time, the corresponding array is stored;

5. Start hook

aspect_prepareClassAndHookSelector(self, selector, error);

Section 1: Aspects will check the legality of the incoming parameters before hook, then convert the incoming block (that is, before calling the original method, or replace the code block of the original method) and the original method to convert the method signature to the method signature.AspectIdentifierIn this class (this information will be used when calling this block later), it will be saved to theAspectsContainerIn the corresponding array of this class (finally, get one of them through traversalAspectIdentifierObject, callinginvokeWithInfoMethod), and after the preparation work is finished, we start to hook the classes and methods

2: How do aspects look at classes and methods?

First hook the class and then the selector

1.Hook Class

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    // get class
    Class statedClass = self.class;
    // get class的isa指针
    Class baseClass = object_getClass(self);

    NSString *className = NSStringFromClass(baseClass);

    // Already subclassed
    //Determine whether to include "aspects". If yes, it means it has been hook,
    //If it does not contain "aspects", then judge whether it is a metaclass. If it is a metaclass, call "aspect" swizzleclassinplace
    //If it does not contain "aspects" and is not a metaclass, then judge whether statedclass and baseClass are equal. If they are not equal, it means the object that has been KVO. Because the ISA pointer of the KVO object points to another intermediate class, call "aspect" swizzleclassinplace

    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.
    //If it is not a metaclass or a class that has been KVO or hook, continue to execute and create a subclass,
    //The splicing class name is XXX ﹣ aspects_
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    //Get the class according to the concatenated class name
    Class subclass = objc_getClass(subclassName);
    //If it's nil
    if (subclass == nil) {
        //BaseClass = mainviewcontroller, create a subclass mainviewcontroller? Aspects_
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        //If subclass creation fails, an error is reported
        if (subclass == nil) {
            NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }

        aspect_swizzleForwardInvocation(subclass);
        //Point isa of subclass to statedclass
        aspect_hookedGetClass(subclass, statedClass);
        //Isa of metaclass of subclass also points to statedclass.
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        //Register the newly created subclass subclass, and then call object_setclass (self, subclass); point the current self isa to the subclass subclass
        objc_registerClassPair(subclass);
    }

    object_setClass(self, subclass);
    return subclass;
}
  • judgeclassNameInclude in_Aspects_, if it is included, it means that this class has been directly returned to the ISA pointer of this class after hook
  • If it does not include judging whether it is a metaclass, callaspect_swizzleClassInPlace()
  • If it is not a metaclass, judge againbaseClassandstatedClassEqual or not. If not, it means the object passed by KVO
  • If it is not a metaclass or a class that has been KVO, continue to execute downward, and create a subclass with the name of the original class+_Aspects_, create successful callaspect_swizzleForwardInvocation()Exchange IMP to create new classforwardInvocationIMPReplace with__ASPECTS_ARE_BEING_CALLED__And thensubClassOfisaPointer pointingstatedCass,subclassThe ISA pointer to the metaclass ofstatedClass, and then register the newly created subclasssubClassCall againobject_setClass(self, subclass); point the ISA pointer of the current self to the subclasssubClass

aspect_swizzleClassInPlace()

static Class aspect_swizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    NSString *className = NSStringFromClass(klass);
    //Create unordered collection
    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        //If the collection does not contain classname, add it to the collection
        if (![swizzledClasses containsObject:className]) {
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}

This function is mainly: by callingaspect_swizzleForwardInvocation ()Function toforwardInvocationIMPReplace with__ASPECTS_ARE_BEING_CALLED__, and then add the class name to the collection (which will be used when the hook is deleted later in the collection)

aspect_swizzleForwardInvocation(Class klass)
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    //Replace the imp of forwardinvocation with "requests" are "being" called__
    //Class "replacemethod" returns the imp of the original method
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "[email protected]:@");
   //If originalimplement is not empty, it indicates that the original method has an implementation. Add a new method  aspects  forwardinvocation: point to the original originalimplement. If it cannot be processed in  aspects  are  being  called  there, judge whether there is an implementation  aspects  forwardinvocation, and forward if there is one.

    if (originalImplementation) {
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "[email protected]:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

The exchange method implements imp, andforwardInvocation:Replace imp of with__ASPECTS_ARE_BEING_CALLED__The purpose of this is to point the imp of the original method to objc [u forward] and then callforwardInvocation :BecauseforwardInvocation :The imp of points to__ASPECTS_ARE_BEING_CALLED__The function will eventually be called here, where the hook code and the original method are executed. If the original class has an implementationforwardInvocation :This method points the imp of this method to__aspects_forwardInvocation:

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

According to the passed parameters, point the imp of the class method of the newly created class and its metaclass to the original class (call the class method again for the newly created class in the future, and all returned are statedclass)

object_setClass(self, subclass);

Point the ISA pointer of the original class to the newly created class

Let’s talk about how to hook the method

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 the subclass cannot respond to aspects XXXX, add the aspects XXXX method to Klass. The implementation of the method is the implementation of the native method
        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));
    }
}

The above code is mainly to hook the selector, first get the original method, and then judge whether it points to the_objc_msgForwardIf not, get the method code of the original method and add a method for the new subclassaspects__xxxxx, and point the imp of the new method to the original method, and then point the imp of the method of the original class to_objc_msgForward,hookComplete

3: Requests? Are? Being? Called

static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    //Get the original selector
    SEL originalSelector = invocation.selector;
    //Get the method with the prefixes "aspects" XXXX
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    //Replace selector
    invocation.selector = aliasSelector;
    //Get the container objectcontainer of the instance object. Here is the object associated with aspect "add"
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    //Get the class object container
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    //Initializing aspectinfo, passing in self and invocation parameters
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;

    // Before hooks.
    //Call macro definition to execute aspects slicing function
    //Two things are done in the macro definition: one is to execute 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.
    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)];
}
#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 {
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    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.
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }

    void *argBuf = NULL;
    //Put the parameter in originalinvocation
    for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
        const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
        NSUInteger argSize;
        NSGetSizeAndAlignment(type, &argSize, NULL);

        if (!(argBuf = reallocf(argBuf, argSize))) {
            AspectLogError(@"Failed to allocate memory for block invocation.");
            return NO;
        }

        [originalInvocation getArgument:argBuf atIndex:idx];
        [blockInvocation setArgument:argBuf atIndex:idx];
    }

    [blockInvocation invokeWithTarget:self.block];

    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}

Get data transfer toaspect_invokeInside, callinvokeWithInfo, execute the tangent code block. After executing the code block, get the newly created class and judge whether it can respondaspects__xxxxMethod, nowaspects__xxxxThe method points to the imp implemented by the original method. If it can respond, you can use the[invocation invoke]; call this method, if not responding__aspects_forwardInvocation:This method, which was mentioned in hookclass, points to the imp pointer of the original classforwardInvocation:Implement, execute if you can respond, throw an exception if you can’t responddoesNotRecognizeSelector;
The whole process is about that, and there is a removal operation at the end

4: Remove aspects

- (BOOL)remove {
    return aspect_remove(self, NULL);
}
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;
}

Call the remove method and emptyAspectsContainerData inside, callaspect_cleanupHookedClassAndSelectorClear more data

// Will undo the runtime changes made.
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;
    }

    // Check if the method is marked as forwarded and undo that.
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    //Determine if the selector points to objc msgforward
    if (aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Restore the original method implementation.
        //Get method code
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        //Splice selector
        SEL aliasSelector = aspect_aliasForSelector(selector);
        Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
        //Get the imp of the aspects XXXX method in the newly added class
        IMP originalIMP = method_getImplementation(originalMethod);
        NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
        //The imp of the aspects ﹣ XXXX method refers to the method of the metaclass class
        class_replaceMethod(klass, selector, originalIMP, typeEncoding);
        AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }

    // Deregister global tracked selector
    aspect_deregisterTrackedSelector(self, selector);

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

The above codes mainly do the following:

  • Get whether the imp of the method of the original class points to objc msgforward. If so, point back to the imp of the method
  • Delete the data in swizzledclasses if it is a metaclass
  • Point the ISA pointer of the new class to the original class
  • In fact, it’s to restore the processing done in hook

**There are probably so many things. If you don’t understand or write wrong, you are welcome to point out. You are also welcome to join the group to discuss, learn and grow together
Click to group (note 123)**