Detailed Aspects Framework for iOS

We must ask ourselves such tasks: first, to learn, second to learn, and third to learn.- Lenin

Today I watched a live Aspects sharing, so I want to write an article to share Aspects with you

One: What are Aspects?

Aspects Is an open source library for facet-oriented programming that allows you to include any code in the methods that exist in each class and instance.You can execute the method before or after it is executed, or you can replace the original method.Hook is implemented through Runtime message forwarding.Aspects automatically handles superclasses, making them easier to use than regular method calls, with Star on github more than 6000 and more stable.

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

Aspects is preparation for 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 using the above method, pass in SEL (method to Hook), options (call before or after remote method call or replace), block (code to execute), 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(^{
        //Judge the validity of the parameter first, and return nil directly if it is not valid
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            //Parameter legal
            //Create Container
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            //Create an AspectIdentifier object (save hook contents)
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                //Add identifier s to containers (add to different collections, depending on options)
                [aspectContainer addAspect:identifier withOptions:options];

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

The above methods are mainly divided into the following steps:

  • Determine the validity of the method passed in above
  • If it is legal to create an AspectsContainer container class, it will be classified according to the timing of the incoming slicing and added to the corresponding collection
  • Create an AspectIdentifier object to save the hook contents
  • If the AspectIdentifier object is created successfully, add the AspectIdentifier to the corresponding array based on options
  • Finally call aspect_prepareClassAndHookSelector(self, selector, error); start hook ing

Next, explain each of the above steps

1. Judging the legality of the incoming method

/*
 Judging the validity of parameters:
 1.First add retain, release, autorelease, forward Invocation to the array, if the SEL is one of the arrays, error occurs
 And returns NO, which are all methods that cannot be swizzled
 2.Whether the timing of the incoming is correct, whether the SEL is dealloc, and if dealloc, the timing of the selected call must be AspectPositionBefore
 3.Determines whether a class or class object responds to an incoming sel
 4.If class methods are replaced, check for duplicate replacements
 */
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 dealloc 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;
    }
    //Determining whether a method can be responded to, respondsToSelector (determining whether an object responds to a method), instancesRespondToSelector (determining whether a class responds 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 if 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 parameters passed in are legal and not hook ed, so you can save the information
        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 code above does a few main things:

  • Add "retain", "release", "autorelease", "forward Invocation: to the set to determine if the set contains an incoming selector, and if it contains a return NO, Aspects cannot hook these functions.
  • Determine if selector is a dealloc method, if it is a facet timing it must be AspectPositionBefore, otherwise it will error and return NO, after dealloc the object will be destroyed, so the slicing timing can only be called before the original method is called
  • Determining whether class and instance objects can respond to incoming selector s does not return NO
  • If it is a metaclass, if it is a metaclass, if the method has not been hooked, if it does not, then the data will be saved, and if a method can only hook once within the hierarchy of a class

2. Create AspectsContainer container class

// Loads or creates the aspect container.
static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    //Stitching string aspects_u viewDidAppear:
    SEL aliasSelector = aspect_aliasForSelector(selector);
    //Get the aspectContainer object
    AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
    //Create if not obtained above
    if (!aspectContainer) {
        aspectContainer = [AspectsContainer new];
        objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
    }
    return aspectContainer;
}

Gets its corresponding AssociatedObject associated object, and if it is not, creates an associated object.Finally, the selector is prefixed with "aspects_", corresponding to the aspectContainer.

3. Create an AspectIdentifier object to save the hook contents

+ (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 of the method to be replaced with the original method, and if different, does not proceed
    //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 a method signature
 2.Then compare the number of parameters of the two signatures, 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 a method signature
    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
    //Determine the number of parameters for the two method numbers
    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        //If the first parameter of blockSignature is taken out is _cmd, the corresponding type is'@', and 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;
                }
            }
        }
    }
    //Throw an exception if all signaturesMatch es compared above are NO
    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 converts blcok to a method signature
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
    //Determines whether there is an AspectBlockFlagsHasSignature flag bit and does not report an error that does not contain a 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, then compares it with the original method signature, and assigns if NO is returned differently

4. Add AspectIdentifier to the corresponding array based on 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;
    }
}

Store the corresponding array according to the incoming slicing timing;

5. Start hook ing

aspect_prepareClassAndHookSelector(self, selector, error);

Subsection: Aspects validates the validity of incoming parameters before hooking, then compares the incoming block (that is, before, after, or to replace the code block of the original method) with the original method by converting it to a method signature, and if it is consistent, saves all the information into the AspectIdentifier class (which is used later when calling this block)Information) is then saved to the corresponding array in the AspectsContainer class according to the incoming slicing time (finally, by traversing, one of the AspectIdentifier objects is retrieved, invokeWithInfo method is invoked), and when the preparation is complete, Hook operations on classes and methods begin

Two: How does Aspects Hook classes and methods?

hook the class before hooking the selector

1.Hook Class

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    //Get Classes
    Class statedClass = self.class;
    //Get the isa pointer for the class
    Class baseClass = object_getClass(self);

    NSString *className = NSStringFromClass(baseClass);

    // Already subclassed
    //Determine if _Aspects_ is included and if so, it is hook ed.
    //If it does not contain _Aspects_, then determine if it is a metaclass or if it calls aspect_swizzleClassInPlace
    //If it does not contain _Aspects_, nor is it a metaclass, then judge if statedClass and baseClass are equal. If they are not, the object that was kvo is called because the isa pointer of the kvo object points to another intermediate class, calling 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, a kvo class, or a hook class, proceed to create a subclass.
    //Stitching class named XXX_Aspects_
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    //Getting classes from stitched class names
    Class subclass = objc_getClass(subclassName);
    //If the above is nil
    if (subclass == nil) {
        //baseClass = MainViewController, create a subclass MainViewController_Aspects_
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        //Error if subclass creation fails
        if (subclass == nil) {
            NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
            AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
            return nil;
        }

        aspect_swizzleForwardInvocation(subclass);
        //Pointing isa of subclass to statedClass
        aspect_hookedGetClass(subclass, statedClass);
        //The isa of the subclass's metaclass also points to the statedClass.
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        //Register the subclass you just created and call object_setClass(self, subclass); point the isa of the current self to the subclass
        objc_registerClassPair(subclass);
    }

    object_setClass(self, subclass);
    return subclass;
}
  • Determines whether the className contains _Aspects_, and if it does, it means that the class has been passed an isa pointer that directly returns the class by Hook
  • If it does not contain a judgment whether it is a metaclass or not, call aspect_swizzleClassInPlace()
  • If it is neither included nor metaclass, then judge if the baseClass and statedClass are equal, if not, then it is the object that has been KVO
  • If it is not a metaclass or a class that has not been kvo, proceed down, create a subClass with the original class name +_Aspects_, create a successful call to aspect_swizzleForwardInvocation() to exchange IMP, replace the forward InvocationIMP of the new class with u ASPECTS_ARE_BEING_CALLED_u, and point the isa pointer of the subClass to statedCass, and the isa pointer of the subClass to sub statedClass, otherwiseThen register the newly created subClass and call object_setClass(self, subclass); point the isa pointer of the current self to the subClass

aspect_swizzleClassInPlace()

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

The main function is to replace the forward InvocationIMP of the class with u ASPECTS_ARE_BEING_CALLED_u by calling the aspect_swizzleForwardInvocation () function, then add the class name to the collection (which will be used when Hook is later removed from 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 IMP of forward Invocation with u ASPECTS_ARE_BEING_CALLED_u
    //class_replaceMethod returns the IMP of the original method
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
   // If the originalImplementation is not empty, the original method is implemented. Add a new method u aspects_forward Invocation: pointing to the original originalImplementation. If it cannot be handled there, determine if u aspects_forward Invocation is implemented or forward if it can.

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

Exchange methods implement IMP by replacing the IMP of forwardInvocation: with u ASPECTS_ARE_BEING_CALLED_u. The purpose of this is to point the IMP of the original method to objc_forward after hooking the selector, and then call forwardInvocation: u ASPECTS_ARE_BEING_CALLED_ because the IMP of forwardInvocation: points to the u ASPECTS_ARE_BEING_CALLED_u function, which will eventually be called here, persistentlyLine the hook code and the original method, if the original class implements forwardInvocation: This method, point the IMP of this method to u 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));
}

Based on the passed parameters, point the IMP of the newly created class and its metaclass class's class method to the original class (the new class calls the class method again, and returns statedClass)

object_setClass(self, subclass);

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

Next, how to hook a 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 there is no response to aspects_xxx in the subclass, then add aspects_xxxx method to klass, which is implemented by 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 mainly hooks the selector, first gets the original method, then decides if it points to _objc_msgForward or not, then gets the method code of the original method, adds a method aspects_u XXXX to the newly created subclass, points the IMP of the new method to the original method, then points the IMP of the original class to _objc_msgForward, and hooks the new method.

Three: ASPECTS_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 a method with aspects_xxx prefix
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    //Replace selector
    invocation.selector = aliasSelector;
    //Get the container objectContainer for the instance object, which was previously associated with aspect_add
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    //Get the class object container classContainer
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    //Initialize AspectInfo, passing in self, invocation parameters
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;

    // Before hooks.
    //Call macro definition to perform Aspects slicing
    //Inside the macro definition, there are two things to do: one is to execute the [aspect invokeWithInfo:info] method, and the other is to add spects that need to be removed to the array that is 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 parameters 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 the data to pass into aspect_invoke, call invokeWithInfo, execute the faceted code block, and after executing the code block, get the newly created class to determine if it can respond to the aspects_u XXXX method. Now the aspects_u XXX method points to the IMP implemented by the original method, and if it can respond, it calls [invocation invoke]; if it cannot, it calls u aspects_forwardInvocation: This method, mentioned in hookClass, has an IMP pointer pointing to forwardInvocation: implementation in the original class, which can be executed in response to it or throw an exception doesNotRecognizeSelector in response to it;
That's all about the whole process, and there's one final removal

Four: 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, then empty the data inside the AspectsContainer, and call the aspect_cleanupHookedClassAndSelector to clear 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 Encoding
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        //Stitching selector
        SEL aliasSelector = aspect_aliasForSelector(selector);
        Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
        //Gets the IMP of the aspects_u XXX method in the newly added class
        IMP originalIMP = method_getImplementation(originalMethod);
        NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
        //IMP of aspects_u XXX method refers back to the method of 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 code mainly does the following things:

  • Gets if the IMP of the method of the original class points to _objc_msgForward, and if so points back the IMP of the method
  • Delete data in swizzledClasses if it is a metaclass
  • Point the isa pointer of the new class to the original class
  • In fact, the hook time processing, and restored

**There are probably so many things. If you have something you don't understand or have written correctly, you are welcome to point out that you are also welcome to join the group to exchange discussions, learn from and grow together.
Click to group (Note 123)**

Tags: iOS Programming github less encoding

Posted on Mon, 02 Dec 2019 00:58:32 -0500 by delassus