preface

AOP(aspect-oriented Programming), also known as “section-oriented programming”, is a kind of technology that realizes unified maintenance of program functions through pre-compilation and runtime dynamic proxy. In simple terms, it can achieve business isolation, decoupling and so on. AOP technology in the __JAVA__ Spring framework has provided a very comprehensive and mature solution. While iOS and other mobile devices don’t have a lot of use for this, there are some excellent tripartite libraries, such as Aspects, which we’ll talk about next.

So when is AOP appropriate for us to use

  • When we execute a method, we perform a security check on the method (for example, array out of bounds on NSArray)
  • Log certain operations
  • When interacting with the shopping cart, suggestions or hints are triggered based on the user’s actions
  • More radical, it can also be used to remove base class inheritance, as in my architecture example: Nonbaseclass-mvVm-reactiveObjc

People will say that traditional OOP(Object Oriented Programming), that is, object-oriented Programming, is also fully capable of achieving these functions. Yes, that’s true, but a good OOP architecture should be single responsibility, and adding extra aspect requirements means breaking single responsibility. For example, if a Module is only responsible for orders, but you add security checks, logging, suggestions, etc., the Module will be difficult to understand and maintain, and the entire application will be riddled with logging, security checks, and so on.

AOP is a good medicine to solve these problems.

__Aspects__ Basic usage

__Aspects__ is also very simple to use, requiring only two simple interfaces that support both class Hook and instance Hook, providing more detailed operations

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

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

For example, we need to count the number of times a user enters a ViewController

[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];
Copy the code

From there, we don’t have to add ugly code to the base class

Analysis of Aspects Architecture

swizzledClassesDictGlobal dictionary

Access interface

static NSMutableDictionary *aspect_getSwizzledClassesDict() {
    static NSMutableDictionary *swizzledClassesDict;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        swizzledClassesDict = [NSMutableDictionary new];
    });
    return swizzledClassesDict;
}
Copy the code

Store object type

This global dictionary records the tracking information object AspectTracker for the classes of the Hook and their superclasses

swizzledClassesGlobal collection

Access interface

static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
    static NSMutableSet *swizzledClasses;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        swizzledClasses = [NSMutableSetnew]; }); @synchronized(swizzledClasses) { block(swizzledClasses); }}Copy the code

SwizzledClasses are a global collection in which the names of the object classes where the object types are hooked are stored

AspectBlockRef

AspectBlockRef in Aspects, which is the Block in usingBlock that we use as an interface parameter, is in the following form in the example above

^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
}
Copy the code

The source code for AspectBlockRef is shown below

typedef NS_OPTIONS(int, AspectBlockFlags) {
	AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
	AspectBlockFlagsHasSignature          = (1 << 30)};/////////
typedef struct _AspectBlock {
	__unused Class isa;
	AspectBlockFlags flags;
	__unused int reserved;
	void (__unused *invoke)(struct _AspectBlock *block,...). ;struct {
		unsigned long int reserved;
		unsigned long int size;
		// requires AspectBlockFlagsHasCopyDisposeHelpers
		void (*copy)(void *dst, const void *src);
		void (*dispose)(const void *);
		// requires AspectBlockFlagsHasSignature
		const char *signature;
		const char *layout;
	} *descriptor;
	// imported variables
} *AspectBlockRef;
Copy the code

It looks a little complicated, but the Block we’ve been using is a structure like this, where AspectBlockRef is really ___GloablBlock__, AspectBlockRef is just a name change, it’s essentially a Block luckily, Apple has open-source code for Block: The Block implementation source code portal gives us a sneak peek

/////////////////////// Block_private.h

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if ! BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT= (1 << 31)  // compiler
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref countint32_t reserved; void (*invoke)(void *, ...) ;struct Block_descriptor_1 *descriptor;
    // imported variables
};
Copy the code

Flags represents the operand of the Block

// AspectBlockRef
AspectBlockFlags flags;   
// Block_private.h
volatile int32_t flags; // contains ref count
Copy the code

There are only two flags in Aspects

AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
AspectBlockFlagsHasSignature          = (1 << 30)
Copy the code

The corresponding Block definition enumeration

BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
Copy the code
  • When a Block is copiedflag & AspectBlockFlagsHasCopyDisposeHelpersWhen true, the Block layout will be addedBlock_descriptor_2
  • When a Block has a method signatureflag & AspectBlockFlagsHasSignatureIf true, the Block layout existsBlock_descriptor_3

The invoke pointer simply changes its first argument from generic to ___AspectBlock__

// AspectBlockRef
void (__unused *invoke)(struct _AspectBlock *block,...). ; //Block_private.h
void (*invoke) (void*,...). ;Copy the code

We’re going too far. Stop.

AspectToken

The protocol defined in Aspects to undo hooks

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
Copy the code

The Hook method returns a method that complies with the AspectToken protocol. To cancel the corresponding Hook, simply call remove, the protocol method of the proxy object

AspectInfo

The AspectInfo object follows the __AspectInfo__ protocol (of the same name), which represents aspect information and is the first parameter in AspectBlockRef above

  • Instance: the instance that is currently hooked
  • Origin Invocation: The original NSInvocation object that is currently hooked
  • Arguments: Arguments to all methods, a calculation property, which has a value only when called

Specifically, the parameters are from the NSInvocation

NSMutableArray *argumentsArray = [NSMutableArray array];
for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
	[argumentsArray addObject:[selfaspect_argumentAtIndex:idx] ? :NSNull.null];
}
Copy the code

Here the parameter subscript starts at 2, since 0,1 already corresponds to the __ message acceptance object __ and selector, respectively. For the parameter boxing details, look at the internal NSInvocation classification for Aspects

AspectIdentifier

In fact, AspectIdentifier is the return value of aspect_hookSelector

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
Copy the code

AspectIdentifier provides an implementation of the remove method, however I do not see AspectIdentifier declaring compliance with the ____ protocol in the source code

The following method encapsulates the information of the class that needs hook. Inside the method, the Block in the usingBlock parameter is checked for adaptation

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error 
{
    // Get the Block's method signature
    NSMethodSignature *blockSignature =   aspect_blockMethodSignature(block, error); // TODO: check   signature compatibility, etc.
    // Compatibility check
    if(! aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {return nil;
    }
    // Hook information encapsulation
    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;
}
Copy the code
AspectsContainer

AspectsContainer as the name implies, AspectIdentifier is a faceted container class. The AspectIdentifier is placed into different containers based on different options

  • NSArray *beforeAspects corresponds to the AspectPositionBefore option
  • NSArray *insteadAspects corresponds to the AspectPositionInstead option
  • NSArray *afterAspects corresponds to the AspectPositionAfter option

Note that __Asepcts__ is retrieved from the associated object using the aspect_getContainerForObject method

static AspectsContainer *aspect_getContainerForObject(NSObject *self.SEL selector) {
     // Get method alias (aspects_ prefix for original method)
    SEL aliasSelector = aspect_aliasForSelector(selector);
    AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
    if(! aspectContainer) { aspectContainer = [AspectsContainer new];
       // Associate the AspectsContainer object with a method alias
        objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
    }
    return aspectContainer;
}
Copy the code
AspectTracker

AspectTracker represents the tracking of aspects, stored in the global dictionary swizzledClassesDict, and traces record information up from subclasses

  • SelectorNames records the method name of the class currently being traced that requires a hook
  • selectorNamesToSubclassTrackersthroughaddSubclassTracker: hookingSelectorNameRecord subclassAspectTrackerobject
// Add the selector as being modified.
AspectTracker *subclassTracker = nil;
do {
    tracker = swizzledClassesDict[currentClass];
    if(! tracker) { tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
        swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
    }
    if (subclassTracker) {
        [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
    } else {
        [tracker.selectorNames addObject:selectorName];
    }

    // All superclasses get marked as having a subclass that is modified.
    subclassTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
Copy the code
Core Aspects analysis

Methods the entrance

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

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

Aspects supports hooks to both classes and instances, the core of which is the aspect_Add method

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

    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        // Determine whether the Hook class is allowed
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            // Get the associated container
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            // The hook information is encapsulated as an AspectIdentifier object
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];

                // Hook core operation
                aspect_prepareClassAndHookSelector(self, selector, error); }}});return identifier;
}
Copy the code

Basically, the aspect_Add core operation has three steps

  • Determine whether to allow the hooks __aspect_isSelectorAllowedAndTrack__ method
  • Hook information is encapsulated as an AspectIdentifier object
  • Aspect_prepareClassAndHookSelector method performs the hooks (core operation)

The next step is to parse it

aspect_isSelectorAllowedAndTrackDetermine whether a Hook is allowed

1. Blacklist filtering

static NSSet *disallowedSelectorList;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
    disallowedSelectorList = [NSSet setWithObjects:@"retain"The @"release"The @"autorelease"The @"forwardInvocation:".nil];
});

NSString *selectorName = NSStringFromSelector(selector);
if ([disallowedSelectorList containsObject:selectorName]) {
    ......
    return NO;
}
Copy the code

Special methods do not allow hooks

2, The dealloc method hook only allows the AspectPositionBefore option

AspectOptions position = options&AspectPositionFilter;
if ([selectorName isEqualToString:@"dealloc"] && position ! =AspectPositionBefore) {...return NO;
}
Copy the code

3. Filter unresponsive methods

if(! [selfrespondsToSelector:selector] && ! [self.class instancesRespondToSelector:selector]) {
    return NO;
}
Copy the code

4. Instance and class filtering

But before that, the concept of metaclasses

Definition of a metaclass: A metaclass is a class of class objects

Also note that class_isMetaClass(object_getClass(self)) uses an object_getClass method instead of a method like [obj].

object_getClass [obj class]
Instance objects Isa pointer to Isa pointer to
Class object Isa pointer to The object itself
Yuan class object Isa pointer to The object itself

Ok, now let’s look at the code for filtering

if (class_isMetaClass(object_getClass(self))) {... }else{
	return YES;
}
Copy the code

Class_isMetaClass (object_getClass(self)) is used to determine whether a hook object is an instance or a class

NSObject *obj = [[NSObject alloc] init];
[obj aspect_hookSelector:@selector(copy) withOptions:AspectPositionAfter usingBlock:^(id <AspectInfo> info){
} error:nil];

Class getClass = object_getClass(self);
BOOL isMeta = class_isMetaClass(getClass); / / NO printing
Copy the code

If it’s an instance object, then getClass, which is an ISA pointer, points to the corresponding class, and class_isMetaClass says NO

[NSObject aspect_hookSelector:@selector(copy) withOptions:AspectPositionAfter usingBlock:^(id <AspectInfo> info){

} error:nil];

Class getClass = object_getClass(self);
BOOL isMeta = class_isMetaClass(getClass); / / print YES
Copy the code

If it’s a class object, then getClass, which is an ISA pointer, points to the metaclass, and class_isMetaClass is YES

If class_isMetaClass(object_getClass(self)) returns YES, that is, the Hook object is a class object

Class klass = [self class];
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(a);Class currentClass = [self class]; / / havehookThe method of passing is not repeatedhook
AspectTracker *tracker = swizzledClassesDict[currentClass];
if ([tracker subclassHasHookedSelectorName:selectorName]) {
    return NO;
}

// Look up the parent class
do {
    tracker = swizzledClassesDict[currentClass];
    if ([tracker.selectorNames containsObject:selectorName]) {
        if (klass == currentClass) {
            // If it is already traversed to the top level of the parent class
            return YES;
        }
        // Methods that have already been hooked will no longer be repeatedly hooked
        return NO; }}while ((currentClass = class_getSuperclass(currentClass)));

// Look up the parent class to generate AspectTracker information
currentClass = klass; 
AspectTracker *subclassTracker = nil;
do {
    tracker = swizzledClassesDict[currentClass];
    if(! tracker) { tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
        swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
    }
    if (subclassTracker) {
        [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
    } else {
        [tracker.selectorNames addObject:selectorName];
    }

    // All superclasses get marked as having a subclass that is modified.
    subclassTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
Copy the code

So, the hook instance method doesn’t need to traverse up through the parent method, which is intuitive and logical

Aspects Hook has done a very sound pre-check and is well worth learning!

Hook information is encapsulated as an AspectIdentifier object

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {

    NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
    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;
}
Copy the code

Method first extracts the Block’s method signature through aspect_blockMethodSignature

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
    // Determine whether a block has a method signature by determining the block's flags
    if(! (layout->flags &AspectBlockFlagsHasSignature)) {
        return nil;
    }
    / / access descriptor
    void *desc = layout->descriptor;
    /* Calculate the memory address offset to determine the location of signature */
    // Add the offset of reserved and size (both types are unsigned long int so multiply by 2)
    desc += 2 * sizeof(unsigned long int);
    // If there are flags of copy and dispose, add the corresponding offset
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
        desc += 2 * sizeof(void *);
    }
    if(! desc) {return nil;
    }
    // Get the real method signature
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}
Copy the code

Now that we know how to get a Block method signature, we can first understand the memory layout of AspectBlockRef explained in this article, and then understand this code

(*(const char **)desc)

Struct {const char *signature; // Descriptor is a pointer to a structure that stores the starting address of the structure. } *descriptor; // Assign the structure pointer to the generic pointer desc void *desc = Layout -> Descriptor; // Signature's type is const char *, Const char ** obj = (const char **)desc; const char ** obj = (const char **)desc; const char ** obj = (const char **)desc; const char *signature = *obj;Copy the code

Yeah, that’s fine. Next tip, which is also our highlight

Aspect_prepareClassAndHookSelector method performs the hooks

We’ll look at aspect_prepareClassAndHookSelector source

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

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

        // We use forwardInvocation to hook in.
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); }}Copy the code

What is important and central to the source code here is the aspect_hookClass method

static Class aspect_hookClass(NSObject *self.NSError **error) {
    NSCParameterAssert(self);
        // class
	Class statedClass = self.class;
       / / isa pointer
	Class baseClass = object_getClass(self);
	NSString *className = NSStringFromClass(baseClass);

    // subclassed, that is, subclassed with the suffix "_Aspects_"
	if ([className hasSuffix:AspectsSubclassSuffix]) {
		return baseClass;
	}
    // if the Hook is a class invocation, then muddle the ForwardInvocation (the class invocation points to itself)
    else if (class_isMetaClass(baseClass)) {
        return aspect_swizzleClassInPlace((Class)self);
    }
    // if the Hook is an instance that has been subclassed by kvo, we need to blend its metaClass ForwardInvocation method
    else if(statedClass ! = baseClass) {return aspect_swizzleClassInPlace(baseClass);
    }

    // mix up the instance objects

   // Add the default suffix _Aspects_
	const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
   // Get the class with the default suffix added
	Class subclass = objc_getClass(subclassName);

	if (subclass == nil) {
    // If you haven't already created a subclass dynamically
		subclass = objc_allocateClassPair(baseClass, subclassName, 0);
		if (subclass == nil) {
            return nil;
        }
        // Mix the forwardInvocation: method
		aspect_swizzleForwardInvocation(subclass);
        // Replace the CLASS IMP pointer
        // subClass.class = statedClass
		aspect_hookedGetClass(subclass, statedClass);
        // subClass.isa.class = statedClass 
		aspect_hookedGetClass(object_getClass(subclass), statedClass);
        // Register a new class
		objc_registerClassPair(subclass);
	}
    // Overwrite the isa pointer to the new class
	object_setClass(self, subclass);
	return subclass;
}
Copy the code

It’s worth noting

// If the Hook is a class object, then write the class object (the pointer to the class object points to itself)
Class baseClass = object_getClass(self);
else if (class_isMetaClass(baseClass)) {
    return aspect_swizzleClassInPlace((Class)self);
}
// If the Hook is an instance that has been subclassed by KVO, we need to mix its metaClass
else if(statedClass ! = baseClass) {return aspect_swizzleClassInPlace(baseClass);
}
Copy the code

Class_isMetaClass (baseClass) ¶ class_isMetaClass(baseClass) ¶ What does baseClass mean

First we need to know how KVO works: when viewing object A, the KVO mechanism dynamically creates A subclass of object A’s current class and overrides the setter method for the observed property keyPath for the new subclass

Object_getClass and [obj class] refer to the same object. When a hook is subclassed by KVO, the isa pointer to the object_getClass object points to the same object as the class pointer

We also learned the process of creating a class on the fly

  • objc_allocateClassPair
  • class_addMethod
  • class_addIvar
  • objc_registerClassPair

Summary: the hook on the class is by mixing the forwardInvocation from the class and the hook on the instance is by subclassing and mixing the forwardInvocation from the subclass

Emmmmm, let’s talk about mixing __forwardInvocation__ in some detail

Write forwardInvocation aspect_swizzleForwardInvocation mix

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

The above code will be class forwardInvocation method IMP replace __ ASPECTS_ARE_BEING_CALLED __, originally associated with AspectsForwardInvocationSelectorName IMP

Next let’s look at the __ ASPECTS_ARE_BEING_CALLED __ method

static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self.SEL selector, NSInvocation *invocation) {

    SEL originalSelector = invocation.selector;
	SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    invocation.selector = aliasSelector;
    // Get the instance's container
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    // Get the container of the class
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;

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

    // Instead hooks.
    BOOL respondsToAlias = YES;
    if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
        // Perform operations in insteadAspects
        aspect_invoke(classContainer.insteadAspects, info);
        aspect_invoke(objectContainer.insteadAspects, info);
    }else {
         // The normal execution method is appended to determine whether it can respond to the method
        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(! respondsToAlias) { invocation.selector = originalSelector;SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);

        // How does the hook object not respond to aliasSelector then execute the original forwardInvocation
        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL.NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            DoesNotRecognizeSelector if the class does not implement the forwardInvocation method
            [selfdoesNotRecognizeSelector:invocation.selector]; }}// Remove AspectIdentifier from aspectsToRemove and execute the remove method of AspectIdentifier
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
Copy the code

The above code performs the corresponding beforeAspects, insteadAspects, afterAspects if there is an insteadAspects operation then the insteadAspects operation, otherwise aliasSelector, If the aliasSelector is not responded, the forwardInvocation method before the hook will be executed, and if the forwardInvocation is not implemented, the doesnot need cognizeselector exception will be raised

Let’s look at the details of the Aspect_Invoke implementation aspect

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

The macro definition implements the invokeWithInfo: method __AspectIdentifier__

- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    NSInvocation *originalInvocation = info.originalInvocation;
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;

    // Check whether the parameters match
    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
        return NO;
    }

    // Set parameters
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }
    
    // the originalInvocation parameter is iterated over and the parameter value is copied to the blockInvocation
	void *argBuf = NULL;
    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))) {return NO;
		}
        
		[originalInvocation getArgument:argBuf atIndex:idx];
		[blockInvocation setArgument:argBuf atIndex:idx];
    }
    / / blockInvocation execution
    [blockInvocation invokeWithTarget:self.block];
    
    if(argBuf ! =NULL) {
        free(argBuf);
    }
    return YES;
}
Copy the code

The interesting thing here is [blockInvocation setArgument:&info atIndex:1]; So normally, the indices 0 and 1 are target and selector

The blockInvocation method signature is not a selector, but id< AspectInfo >

End, scatter flowers

What else?

  • Aspect_remove cleanup of hooks is not described here

  • For those interested in practice, take a look at my shallow architecture example: Nonbasecls-mvVm-reactiveObjc (dry stuff with code)

  • Finally, I wish everyone a happy New Year, have a good year, and the line and cherish, yao yao da 😘