preface

After two days in the Runtime hospital, I analyzed the implementation principle of AOP. After leaving the hospital, I found that Aspect libraries have not been analyzed in detail, so this article is about how iOS implements Aspect Oriented Programming.

directory

  • 1. Introduction to Aspect Oriented Programming
  • 2. What are Aspects
  • 3. Analysis of four basic classes in Aspects
  • 4. Preparation before Hook
  • 5. Detailed explanation of Aspects Hook process
  • 6. Potholes about Aspects

Aspect Oriented Programming

Aspect-oriented programming (AOP) is a term in computer science referring to a programming paradigm. The paradigm is based on a language construct called aspect, a new modularity mechanism for describing crosscutting concerns in objects, classes, or functions.

The concept of sides stems from improvements in object-oriented programming, but it can also be used to improve traditional functions. Other programming concepts related to the side include meta-object protocols, subjects, mixins, and delegates.

AOP is a technique that implements uniform maintenance of program functionality through precompilation and runtime dynamic proxies.

OOP (object-oriented programming) abstracts and encapsulates the entities, attributes and behaviors of business processes to obtain a clearer and more efficient division of logical units.

AOP, on the other hand, extracts the facets of the business process. It faces a certain step or stage in the process to achieve the isolation effect of low coupling between the parts of the logical process.

OOP and AOP are two different “ways of thinking.” OOP focuses on encapsulating properties and behaviors of objects, while AOP focuses on extracting aspects from steps and phases.

For example, if there is a requirement to determine permissions, the OOP approach is to add permissions before every action. What about logging? Add logging at the beginning and end of each method. AOP is to extract these repetitive logic and operations, using dynamic proxies, to achieve the decoupling of these modules. OOP and AOP are not exclusive, but complementary.

Using AOP in iOS programming can achieve non-invasive. New functionality can be added without changing the previous code logic. It is mainly used to process some cross-cutting systematic services, such as logging, permission management, caching, and object pool management.

Ii. What are Aspects

Aspects is a lightweight library for faceted programming. It allows you to add any code to any method that exists in every class and every instance. Code can be inserted at the following pointcuts: before(executed before the original method)/instead(executed instead of the original method)/after(executed after the original method, default). Implement Hook through Runtime message forwarding. Aspects automatically invoke super methods, making it easier to use Method Swizzling.

The library is stable and is currently used in hundreds of apps. It is also part of PSPDFKit, a framework library for viewing PDF for iOS. The authors finally decided to open source it.

The four basic classes in Aspects are analyzed

Let’s start with the original file.

1.Aspects.h

typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0./// Called after the original implementation (default)
    AspectPositionInstead = 1./// Will replace the original implementation.
    AspectPositionBefore  = 2./// Called before the original implementation.

    AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};Copy the code

An enumeration is defined in the header file. Inside this enumeration is when to call the slice method. By default, AspectPositionAfter is called after the original method has executed. AspectPositionInstead replaces the original method. AspectPositionBefore is to call the slice method before the original method. AspectOptionAutomaticRemoval is on the hook performed automatically removed.


@protocol AspectToken <NSObject>

- (BOOL)remove;

@endCopy the code

Defines a protocol for AspectToken, where the AspectToken is implicit and allows us to call remove to revoke a hook. The remove method returns YES to indicate that the undo succeeded, and returns NO to indicate that the undo failed.


@protocol AspectInfo <NSObject>

- (id)instance;
- (NSInvocation *)originalInvocation;
- (NSArray *)arguments;

@endCopy the code

An AspectInfo protocol is defined. AspectInfo Protocol is the first argument in our block syntax.

The instance method returns the instance currently hooked. Origin invocation method returns the originalInvocation of the hooked method. The arguments method returns arguments to all methods. Its implementation is lazy loading.

There is also a comment in the header that explains the use and attention of Aspects, and it is worth paying attention to.


/** Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects  to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second.  Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe. */Copy the code

Hook message, the message forwarding mechanism of OC used by Aspects. This has some performance overhead. Do not add Aspects to frequently used methods. Aspects are designed to be used by View/Controller code, not methods that hook calls 1000 times per second.

After adding Aspects, an implicit token is returned, which is used to unlog the hook method. All calls are thread-safe.

Thread safety is discussed in detail below. At the very least, we now know that Aspects should not be used in methods such as for loops, which can cause significant performance losses.


@interface NSObject (Aspects)

/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @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;

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

@endCopy the code

These two methods are the only ones in Aspects’ entire library. As you can see here, Aspects are an extension of NSobject, and you can use both methods as long as you’re an NSobject. Both methods have the same name, and the same input and return values. The only difference is that one is a plus method and the other is a minus method. One for hook class methods and one for hook instance methods.

The method has four inputs. The first selector is the original method that’s going to add a section to it. The second parameter is the AspectOptions type, which represents the slice added in the original method before/instead/after. The fourth argument is an error returned.

The important thing is the third entry block. This block copies the signature type of the method being hooked. Block follows the AspectInfo protocol. We can even use an empty block. Parameters in the AspectInfo protocol are optional and are used to match block signatures.

The return value is a token that can be used to deregister this Aspect.

Note that hook static methods are not supported for Aspects


typedef NS_ENUM(NSUInteger, AspectErrorCode) {
    AspectErrorSelectorBlacklisted,                   /// Selectors like release, retain, autorelease are blacklisted.
    AspectErrorDoesNotRespondToSelector,              /// Selector could not be found.
    AspectErrorSelectorDeallocPosition,               /// When hooking dealloc, only AspectPositionBefore is allowed.
    AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed.
    AspectErrorFailedToAllocateClassPair,             /// The runtime failed creating a class pair.
    AspectErrorMissingBlockSignature,                 /// The block misses compile time signature info and can't be called.
    AspectErrorIncompatibleBlockSignature,            /// The block signature does not match the method or is too large.

    AspectErrorRemoveObjectAlreadyDeallocated = 100   /// (for removing) The object hooked is already deallocated.
};

extern NSString *const AspectErrorDomain;Copy the code

The type of error code is defined here. It’s convenient for us to debug when something goes wrong.

2.Aspects.m

#import "Aspects.h"
#import <libkern/OSAtomic.h>
#import <objc/runtime.h>
#import <objc/message.h>Copy the code

#import This header file is imported for the spin lock used below. #import and #import are required headers to use Runtime.


typedef NS_OPTIONS(int, AspectBlockFlags) {
      AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
      AspectBlockFlagsHasSignature          = (1 << 30)};Copy the code

AspectBlockFlags AspectBlockFlags AspectBlockFlags are used to flag whether Copy and Dispose Helpers and method Signature are required.

The four classes defined in Aspects are AspectInfo, AspectIdentifier, AspectsContainer, and AspectTracker. Let’s look at how each of these classes is defined.

3. AspectInfo

@interface AspectInfo : NSObject <AspectInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic.unsafe_unretained.readonly) id instance;
@property (nonatomic.strong.readonly) NSArray *arguments;
@property (nonatomic.strong.readonly) NSInvocation *originalInvocation;
@endCopy the code

AspectInfo corresponding implementation



#pragma mark - AspectInfo

@implementation AspectInfo

@synthesize arguments = _arguments;

- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation {
    NSCParameterAssert(instance);
    NSCParameterAssert(invocation);
    if (self = [super init]) {
        _instance = instance;
        _originalInvocation = invocation;
    }
    return self;
}

- (NSArray *)arguments {
    // Lazily evaluate arguments, boxing is expensive.
    if(! _arguments) { _arguments =self.originalInvocation.aspects_arguments;
    }
    return _arguments;
}Copy the code

AspectInfo is inherited from NSObject and follows the AspectInfo protocol. In its – (ID)initWithInstance: Invocation: method, the instance instance and the original Invocation are saved in the member variable of the AspectInfo class. The – (NSArray *)arguments method is a lazy invocation that returns the original Invocation aspects array.

How is aspects_arguments implemented as a getter? The authors did this by adding a classification for NSInvocation.



@interface NSInvocation (Aspects)
- (NSArray *)aspects_arguments;
@endCopy the code

Add an Aspects class to the original NSInvocation class. Add a single method, AspectS_arguments, that returns an array containing all the parameters of the current Invocation.

Corresponding implementation


#pragma mark - NSInvocation (Aspects)

@implementation NSInvocation (Aspects)

- (NSArray *)aspects_arguments {
 NSMutableArray *argumentsArray = [NSMutableArray array];
 for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
  [argumentsArray addObject:[selfaspect_argumentAtIndex:idx] ? :NSNull.null];
 }
 return [argumentsArray copy];
}

@endCopy the code

Aspects_arguments – (NSArray *)aspects_arguments implements a simple for loop that adds parameters from the methodSignature methodSignature to an array and returns the array.

There are two details about the implementation of this – (NSArray *) Aspects_arguments method that gets all the arguments to a method. One is why the loop starts at 2, and the other is how it is implemented inside [self aspect_argumentAtIndex: IDx].

Let’s talk about why the loop starts at 2.

Type Encodings Complements the Runtime, where the compiler encodes the return value and parameter Type of each method as a string and associates it with the method’s selector. This encoding scheme is useful in other situations as well, so we can get it using the @encode compiler directive. When given a type, @encode returns the string encoding for that type. These types can be primitive types such as ints and Pointers, structures and classes. In fact, any type that can be used as an argument to the sizeof() operation can be used with @encode().

All Type encodings in Objective-C are listed in the Type Encoding section of the Objective-C Runtime Programming Guide. It is important to note that many of these types are the same encoding types we use for archiving and distribution. But some can’t be used while archiving.

Note: Objective-C does not support long double. Encode (long double) returns d, the same as double.

OC supports message forwarding and dynamic invocation. The Type information of objective-C Method is encoded in the form of “return value Type + parameter Types”. Two implicit parameters, self and _cmd, should also be taken into account:


- (void)tap; = >"v@:"
- (int)tapWithView:(double)pointx; = >"i@:d"Copy the code

From the above table, we can see that the first three bits of the encoded string are the return value Type, self implicit parameter Type @, _cmd implicit parameter Type:.

So starting at bit 3, that’s the entry.

Let’s say we print methodSignature using – (void)tapView:(UIView *)view atIndex:(NSInteger)index


(lldb) po self.methodSignature
<NSMethodSignature: 0x60800007df00>
number of arguments = 4
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) The '@'
flags {isObject}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (:) ':'
flags {}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 2: -------- -------- -------- --------
type encoding (@) The '@'
flags {isObject}
modifiers {}
frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 3: -------- -------- -------- --------
type encoding (q) 'q'
flags {isSigned}
modifiers {}
frame {offset = 24, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}Copy the code

Number of arguments = 4, since there are 2 implicit arguments self and _cmd, plus input arguments view and index.

argument return value 0 1 2 3
methodSignature v @ : @ q

The argument is frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}memory {offset = 0, size = 0}. The second argument is self, frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}. Since size = 8, the next frame offset is 8, followed by 16, and so on.

The reason for passing 2 here also depends on the aspect_argumentAtIndex implementation.

Let’s look at a concrete implementation of aspect_argumentAtIndex. This method, thanks to the ReactiveCocoa team, provides an elegant implementation for getting parameters for method signatures.


// Thanks to the ReactiveCocoa team for providing a generic solution for this.
- (id)aspect_argumentAtIndex:(NSUInteger)index {
 const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
 // Skip const type qualifier.
 if (argType[0] == _C_CONST) argType++;

#define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)

 if (strcmp(argType, @encode(id)) = =0 || strcmp(argType, @encode(Class)) == 0) {
    __autoreleasing id returnObj;
    [self getArgument:&returnObj atIndex:(NSInteger)index];
    return returnObj;
 } else if (strcmp(argType, @encode(SEL)) == 0) {
    SEL selector = 0;
    [self getArgument:&selector atIndex:(NSInteger)index];
    return NSStringFromSelector(selector);
} else if (strcmp(argType, @encode(Class)) == 0) {
    __autoreleasing Class theClass = Nil;
    [self getArgument:&theClass atIndex:(NSInteger)index];
    return theClass;
    // Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
 } else if (strcmp(argType, @encode(char)) = =0) {
    WRAP_AND_RETURN(char);
 } else if (strcmp(argType, @encode(int)) = =0) {
    WRAP_AND_RETURN(int);
 } else if (strcmp(argType, @encode(short)) = =0) {
    WRAP_AND_RETURN(short);
 } else if (strcmp(argType, @encode(long)) = =0) {
    WRAP_AND_RETURN(long);
 } else if (strcmp(argType, @encode(long long)) = =0) {
    WRAP_AND_RETURN(long long);
 } else if (strcmp(argType, @encode(unsigned char)) = =0) {
    WRAP_AND_RETURN(unsigned char);
 } else if (strcmp(argType, @encode(unsigned int)) = =0) {
    WRAP_AND_RETURN(unsigned int);
 } else if (strcmp(argType, @encode(unsigned short)) = =0) {
    WRAP_AND_RETURN(unsigned short);
 } else if (strcmp(argType, @encode(unsigned long)) = =0) {
    WRAP_AND_RETURN(unsigned long);
 } else if (strcmp(argType, @encode(unsigned long long)) = =0) {
    WRAP_AND_RETURN(unsigned long long);
 } else if (strcmp(argType, @encode(float)) = =0) {
    WRAP_AND_RETURN(float);
 } else if (strcmp(argType, @encode(double)) = =0) {
    WRAP_AND_RETURN(double);
 } else if (strcmp(argType, @encode(BOOL)) = =0) {
    WRAP_AND_RETURN(BOOL);
 } else if (strcmp(argType, @encode(bool)) = =0) {
    WRAP_AND_RETURN(BOOL);
 } else if (strcmp(argType, @encode(char*)) = =0) {
    WRAP_AND_RETURN(const char *);
 } else if (strcmp(argType, @encode(void(^) (void))) = =0) {__unsafe_unretained id block = nil;
    [self getArgument:&block atIndex:(NSInteger)index];
    return [block copy];
 } else {
    NSUInteger valueSize = 0;
    NSGetSizeAndAlignment(argType, &valueSize, NULL);

    unsigned char valueBytes[valueSize];
    [self getArgument:valueBytes atIndex:(NSInteger)index];

    return [NSValue valueWithBytes:valueBytes objCType:argType];
 }
 return nil;
#undef WRAP_AND_RETURN
}Copy the code

GetArgumentTypeAtIndex: this method is a string used to get the type encoding of the index specified by the methodSignature methodSignature. The string that comes out of this method is the index value that we passed in. For example, if we pass in a 2, the string that comes out is the third bit of the string corresponding to methodSignature.

Since bit 0 is the type encoding corresponding to the return value, the passed 2 corresponds to Argument2. So we pass index = 2, which filters out the first 3 type encoding strings, starting with Argument2. That’s why the loop starts at 2.

_C_CONST is a constant that determines whether the encoding string is CONST.


#define _C_ID       The '@'
#define _C_CLASS    The '#'
#define _C_SEL      ':'
#define _C_CHR      'c'
#define _C_UCHR     'C'
#define _C_SHT      's'
#define _C_USHT     'S'
#define _C_INT      'i'
#define _C_UINT     'I'
#define _C_LNG      'l'
#define _C_ULNG     'L'
#define _C_LNG_LNG  'q'
#define _C_ULNG_LNG 'Q'
#define _C_FLT      'f'
#define _C_DBL      'd'
#define _C_BFLD     'b'
#define _C_BOOL     'B'
#define _C_VOID     'v'
#define _C_UNDEF    '? '
#define _C_PTR      A '^'
#define _C_CHARPTR  The '*'
#define _C_ATOM     The '%'
#define _C_ARY_B    '['
#define _C_ARY_E    '] '
#define _C_UNION_B  '('
#define _C_UNION_E  ') '
#define _C_STRUCT_B '{'
#define _C_STRUCT_E '} '
#define _C_VECTOR   '! '
#define _C_CONST    'r'Copy the code

The Type here is exactly the same as the Type of OC, except that it is a CHAR of C.


#define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)Copy the code

WRAP_AND_RETURN is a macro definition. The getArgument:atIndex: method is used in the NSInvocation to get the Argument based on index and return val as an object.

In the big if-else section below, there are many string comparison functions called STRCMP.

For example, STRCMP (argType, @encode(id)) == 0, argType is a char that methodSignature retrieves for the corresponding Type encoding, the same type encoding as @encode(id). After STRCMP comparison, 0 means the type is the same.

Char, int, short, long, long long, unsigned char, unsigned int, The basic types unsigned short, unsigned long, unsigned long long, float, double, BOOL, BOOL, char * are all returned as objects using WRAP_AND_RETURN. Finally, block and struct structures are judged, and corresponding objects are also returned.

The incoming parameters are then returned to the array and received. – (void)tapView:(UIView *)view atIndex:(NSInteger)index


(
  <UIView: 0x7fa2e2504190; frame = (0 80; 414 40); layer = <CALayer: 0x6080000347c0>>",
  1
)Copy the code

In summary, AspectInfo is mainly NSInvocation. Wrap one layer of NSInvocation, such as parameter information, etc.

4. AspectIdentifier.

// Tracks a single aspect.
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic.assign) SEL selector;
@property (nonatomic.strong) id block;
@property (nonatomic.strong) NSMethodSignature *blockSignature;
@property (nonatomic.weak) id object;
@property (nonatomic.assign) AspectOptions options;
@endCopy the code

Corresponding implementation


#pragma mark - AspectIdentifier

@implementation AspectIdentifier

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
    NSCParameterAssert(block);
    NSCParameterAssert(selector);
    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;
}

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

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p, SEL:%@ object:%@ options:%tu block:%@ (#%tu args)>".self.class, self.NSStringFromSelector(self.selector), self.object, self.options, self.block, self.blockSignature.numberOfArguments];
}

- (BOOL)remove {
    return aspect_remove(self.NULL);
}

@endCopy the code

The aspect_blockMethodSignature method is called in the InstanceType method.



static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
 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];
}Copy the code

The purpose of this aspect_blockMethodSignature is to convert the passed AspectBlock into the method signature of NSMethodSignature.

The structure of AspectBlock is as follows


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

A block type is defined for use inside Aspects. Those of you who are familiar with the blocks of the system will immediately see the similarities. For those unfamiliar, check out my previous article analyzing blocks. In this article, we use Clang to convert blocks into structures that are similar to those defined here.

Now that you know the structure of AspectBlock, it becomes clear to look at the aspect_blockMethodSignature function.


    AspectBlockRef layout = (__bridge void *)block;
 if(! (layout->flags & AspectBlockFlagsHasSignature)) {NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }Copy the code

AspectBlockRef layout = (__bridge void *)block; And then determine whether a AspectBlockFlagsHasSignature logo, if not, the report does not contain the error of the method signature.

Notice that the block passed in is of global type


  (__NSGlobalBlock) __NSGlobalBlock = {
    NSBlock = {
      NSObject = {
        isa = __NSGlobalBlock__
      }
    }
  }Copy the code

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

Desc is the pointer to the descriptor in the original block. The descriptor pointer is offset downward by 2 unsigned long ints to the address of copy. If copy and Dispose are included, then offset downward by 2 (void). The pointer must now move to const char signature. If desc does not exist, an error is reported, and the block does not contain the method signature.


 const char *signature = (*(const char **)desc);
 return [NSMethodSignature signatureWithObjCTypes:signature];Copy the code

At this point, the method signature is guaranteed to exist. Finally, the signatureWithObjCTypes method of NSMethodSignature is called to return the method signature.

Give an example of how aspect_blockMethodSignature ultimately generates a method signature.


    [UIView aspect_hookSelector:@selector(UIView:atIndex:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspects, UIView *view, NSInteger index)
     {

         NSLog("Button clicked on %ld",index);

     } error:nil];Copy the code

Const char *signature ends up with this string


(const char *) signature = 0x0000000102f72676 "v32@? 0@\"
      
       \"8@\"UIView\"16q24"
      Copy the code

v32@? 8 0 @ “” @” UIView “16 q24 is Block

^ (id<AspectInfo> aspects, UIView *view, NSInteger index){

}Copy the code

Corresponding Type. Void returns v of Type, 32 of offset, @? Is the Type of the block, @” “is the first argument, @”UIView” is the second argument, NSInteger is the Type q.

The number following each Type is their respective offset. Print out the final transformed NSMethodSignature.

<NSMethodSignature: 0x600000263dc0> number of arguments = 4 frame size = 224 is special struct return? NO return value: -------- -------- -------- -------- type encoding (v) 'v' flags {} modifiers {} frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0} memory {offset = 0, size = 0} argument 0: -------- -------- -------- -------- type encoding (@) '@? ' flags {isObject, isBlock} modifiers {} frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8} argument 1: -------- -------- -------- -------- type encoding (@) '@"<AspectInfo>"' flags {isObject} modifiers {} frame {offset = 8,  offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8} conforms to protocol 'AspectInfo' argument 2: -------- -------- -------- -------- type encoding (@) '@"UIView"' flags {isObject} modifiers {} frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8} class 'DLMenuView' argument 3: -------- -------- -------- -------- type encoding (q) 'q' flags {isSigned} modifiers {} frame {offset = 24, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8}Copy the code

Back to AspectIdentifier continue to see instancetype method, for the incoming block method signatures, and call the aspect_isCompatibleBlockSignature method.



static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
    NSCParameterAssert(blockSignature);
    NSCParameterAssert(object);
    NSCParameterAssert(selector);

    BOOL signaturesMatch = YES;
    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        if (blockSignature.numberOfArguments > 1) {
            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
            if (blockType[0] != The '@') {
                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) {
            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(! signaturesMatch) {NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
        AspectError(AspectErrorIncompatibleBlockSignature, description);
        return NO;
    }
    return YES;
}Copy the code

What this function does is it compares the method that we’re replacing, block, with the method that we’re replacing. How do you compare? Compare the method signatures of the two.

The selector is the original method.


if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        if (blockSignature.numberOfArguments > 1) {
            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
            if (blockType[0] != The '@') {
                signaturesMatch = NO; }}Copy the code

SignaturesMatch = NO If the number of parameters in the method signature is equal. SignaturesMatch = NO signaturesMatch = NO signaturesMatch = NO signaturesMatch = NO signaturesMatch = NO If both of the above are satisfied, signaturesMatch = YES, then the following more stringent comparison is entered.


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

Here the cycle also starts at 2. Here’s an example of why you start with the second. Let’s go back to the previous example.


[UIView aspect_hookSelector:@selector(UIView:atIndex:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspects, UIView *view, NSInteger index)
 {

     NSLog("Button clicked on %ld",index);

 } error:nil];Copy the code

The original method I’m replacing here is UIView:atIndex:, so the corresponding Type is v@:@q. BlockSignature = v@? Type = v@? Type = v@? @ “@” UIView “q”.

argument return value 0 1 2 3
methodSignature v @ : @ q
blockSignature v @? @ “” @”UIView” q

MethodSignature and blockSignature return values are both void, so they correspond to v. MethodSignature’s argument 0 is the implicit argument self, so it corresponds to @. Argument 0 of the blockSignature argument is a block. . MethodSignature’s argument 1 is the implicit _cmd argument, so it corresponds to:. Argument 1 of blockSignature is, so it corresponds to @””. Argument 2 is the list of arguments after the method signature that may differ and need to be compared.

The last


    if(! signaturesMatch) {NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
        AspectError(AspectErrorIncompatibleBlockSignature, description);
        return NO;
    }Copy the code

If signaturesMatch is NO after the above comparison, then an error is raised and the Block cannot match the method signature.


    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

If the match is successful, all blockSignature values are assigned to AspectIdentifier. This is why AspectIdentifier has a separate attribute NSMethodSignature inside it.

AspectIdentifier has another method, invokeWithInfo.


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

The comment makes it clear that this judgment is written by obsessive-compulsive patients, and that the number of parameters in the block is no greater than the number of parameters in the original method signature.



    // The `self` of the block will be the AspectInfo. Optional.
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }Copy the code

Save AspectInfo 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))) { AspectLogError(@"Failed to allocate memory for block invocation.");
            return NO;
          }

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

    [blockInvocation invokeWithTarget:self.block];Copy the code

The originalInvocation is picked up ina loop, assigned to argBuf, and then to the blockInvocation. The reason why the cycle starts at 2 has already been explained and won’t be repeated here. Finally, assign self.block to the Block Invocation Target.

In summary, AspectIdentifier is the concrete content of a slicing Aspect. It contains the details of the individual Aspect, including the execution time, and the details needed to execute the block: method signatures, parameters, and so on. The process of initializing AspectIdentifier essentially packages the block we pass in as AspectIdentifier.

5. AspectsContainer



// Tracks all aspects for an object/class.
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@endCopy the code

Corresponding implementation


#pragma mark - AspectsContainer

@implementation AspectsContainer

- (BOOL)hasAspects {
    return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0;
}

- (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; }} - (BOOL)removeAspect:(id)aspect {
    for (NSString *aspectArrayName in@ [NSStringFromSelector(@selector(beforeAspects)),
                                        NSStringFromSelector(@selector(insteadAspects)),
                                        NSStringFromSelector(@selector(afterAspects))]) {
        NSArray *array = [self valueForKey:aspectArrayName];
        NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
        if(array && index ! =NSNotFound) {
            NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
            [newArray removeObjectAtIndex:index];
            [self setValue:newArray forKey:aspectArrayName];
            return YES; }}return NO;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>".self.class, self.self.beforeAspects, self.insteadAspects, self.afterAspects];
}

@endCopy the code

AspectsContainer is easier to understand. AddAspect puts the slice Aspects into the corresponding array according to the timing of the slice. RemoveAspects removes all Aspects in a loop. HasAspects determines whether there are Aspects.

AspectsContainer is a container for all Aspects of an object or class. So there will be two types of containers.

It is worth noting that the array is modified by Atomic. With respect to Atomic, it is important to note that by default, methods synthesized by the compiler ensure Atomicity through locking mechanisms. If the property is nonatomic, no synchronization lock is required.

6. AspectTracker



@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
@property (nonatomic.strong) Class trackedClass;
@property (nonatomic.readonly) NSString *trackedClassName;
@property (nonatomic.strong) NSMutableSet *selectorNames;
@property (nonatomic.strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@endCopy the code

Corresponding implementation


@implementation AspectTracker

- (id)initWithTrackedClass:(Class)trackedClass {
    if (self = [super init]) {
        _trackedClass = trackedClass;
        _selectorNames = [NSMutableSet new];
        _selectorNamesToSubclassTrackers = [NSMutableDictionary new];
    }
    return self;
}

- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName {
    return self.selectorNamesToSubclassTrackers[selectorName] ! =nil;
}

- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
    NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
    if(! trackerSet) { trackerSet = [NSMutableSet new];
        self.selectorNamesToSubclassTrackers[selectorName] = trackerSet;
    }
    [trackerSet addObject:subclassTracker];
}
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
    NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
    [trackerSet removeObject:subclassTracker];
    if (trackerSet.count == 0) {[self.selectorNamesToSubclassTrackers removeObjectForKey:selectorName]; }} - (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName {
    NSMutableSet *hookingSubclassTrackers = [NSMutableSet new];
    for (AspectTracker *tracker in self.selectorNamesToSubclassTrackers[selectorName]) {
        if ([tracker.selectorNames containsObject:selectorName]) {
            [hookingSubclassTrackers addObject:tracker];
        }
        [hookingSubclassTrackers unionSet:[tracker subclassTrackersHookingSelectorName:selectorName]];
    }
    return hookingSubclassTrackers;
}
- (NSString *)trackedClassName {
    return NSStringFromClass(self.trackedClass);
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, subclass selector names: %@>".self.class, self.NSStringFromClass(self.trackedClass), self.selectorNames, self.selectorNamesToSubclassTrackers.allKeys];
}

@endCopy the code

The AspectTracker class is used to track classes to be hooked. TrackedClass is the class being traced. TrackedClassName is the class name of the class being traced. SelectorNames is an NSMutableSet, which records the name of the method to be replaced by a hook. NSMutableSet is used to prevent repeated method replacements. SelectorNamesToSubclassTrackers is a dictionary, the key is hookingSelectorName, the value is full of AspectTracker NSMutableSet.

The addSubclassTracker method adds AspectTracker to the corresponding set of SelectorNames. The removeSubclassTracker method removes AspectTracker from the corresponding set of SelectorNames. SubclassTrackersHookingSelectorName method is a set of and check, passing a selectorName, through recursive search, find all contain the selectorName set, finally put the set together as the return value is returned.

Iv. Preparation before Hook

There are just two functions in the Aspects library, one for classes and one for instances.


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

Both implementations call the same method aspect_add with different arguments. So let’s just start with aspect_Add.

-aspect_hookselector :(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error ├ ─ aspect_add(self, selector, options, block, error); └ ─ ─ aspect_performLocked ├ ─ ─ aspect_isSelectorAllowedAndTrack └ ─ ─ aspect_prepareClassAndHookSelectorCopy the code

This is the function call stack. Start with aspect_Add.


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(^{
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];

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

The aspect_add function has five input arguments. The first argument is self, selector is the SEL passed in that needs to be hooked, options is the slicing time, block is the slicing method, and the last error is an error.

Aspect_performLocked is a spin lock. Spinlocks are a more efficient type of lock, much more efficient than @synchronized.


static void aspect_performLocked(dispatch_block_t block) {
    static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
    OSSpinLockLock(&aspect_lock);
    block();
    OSSpinLockUnlock(&aspect_lock);
}Copy the code

If you are not familiar with the 8 big locks in iOS, you can read the following two articles

Lock Understand locks in iOS development

But spin-locks can be problematic: if a low-priority thread obtains the lock and accesses a shared resource, and a higher-priority thread attempts to acquire the lock, it will be in a busy wait state of spin lock and consume a lot of CPU. The low-priority thread cannot compete with the high-priority thread for CPU time, resulting in the task being delayed and unable to release the lock. OSSpinLock is no longer secure

The problem with OSSpinLock is that there is a potential deadlock risk if the threads accessing it are not of the same priority.

The thread is considered to be of the same priority for the time being, so OSSpinLock keeps the thread safe. That is, aspect_performLocked keeps blocks thread-safe.

Now the aspect_isSelectorAllowedAndTrack function and aspect_prepareClassAndHookSelector function.

Then look at aspect_isSelectorAllowedAndTrack function realization process.


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

NSSet NSSet NSSet NSSet NSSet NSSet NSSet NSSet NSSet Retain, release, autorelease, forwardInvocation: is not allowed to be hooked.


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

An error is reported when the name of a selector function is in the blacklist.


    AspectOptions position = options&AspectPositionFilter;
    if ([selectorName isEqualToString:@"dealloc"] && position ! = AspectPositionBefore) {NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
        AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
        return NO;
    }Copy the code

If you want to slice the dealloc, you must slice it before the dealLoc. If it is not before AspectPositionBefore, you must also report an error.


    if(! [selfrespondsToSelector:selector] && ! [self.class instancesRespondToSelector:selector]) {
        NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].".NSStringFromClass(self.class), selectorName];
        AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
        return NO;
    }Copy the code

When the selector is no longer in the blacklist, if the slice is dealloc, and the selector precedes it. This is the time to determine whether the method exists. If the selector is not found in either self or self.class, an error will be reported that the method was not found.


    if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];

        AspectTracker *tracker = swizzledClassesDict[currentClass];
        if ([tracker subclassHasHookedSelectorName:selectorName]) {
            NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
            NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
            NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
            AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
            return NO;
        }Copy the code

Class_isMetaClass determines whether it is a metaclass. The next step is to determine whether the metaclass allows methods to be replaced.

SubclassHasHookedSelectorName will determine whether the current inside the tracker ttf_subclass contains selectorName. Because a method can only be hooked once within a class hierarchy. An error will be reported if the tracker has already been included once.


        do {
            tracker = swizzledClassesDict[currentClass];
            if ([tracker.selectorNames containsObject:selectorName]) {
                if (klass == currentClass) {
                    // Already modified and topmost!
                    return YES;
                }
                NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
                AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
                return NO; }}while ((currentClass = class_getSuperclass(currentClass)));Copy the code

In this do-while loop, currentClass = class_getSuperclass(currentClass) will start with the superclass of currentClass and work up until that class is the root class NSObject.


        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

After checking the validity of the hook above and the class method not allowing repeated substitutions, you can now record the information you want to hook and mark it with AspectTracker. Whenever a subclass is changed, the parent class needs to be marked as well. The termination condition of the do-while is currentClass = class_getSuperclass(currentClass).

The above is the metaclass class method hook to determine the validity of the code.

If it is not a metaclass, hook the “retain”, “release”, “autorelease”, “forwardInvocation “invocation and hook the” dealloc “method must be before. And if the selector can be found, then the method can be hooked.

Once the selector is checked to see if it can be authenticated by a hook, you get or create an AspectsContainer.


// Loads or creates the aspect container.
static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    SEL aliasSelector = aspect_aliasForSelector(selector);
    AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
    if(! aspectContainer) { aspectContainer = [AspectsContainer new]; objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
    }
    return aspectContainer;
}Copy the code

Before reading or creating an AspectsContainer, the first step is to mark the selector.


static SEL aspect_aliasForSelector(SEL selector) {
    NSCParameterAssert(selector);
 return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@ "_ % @".NSStringFromSelector(selector)]);
}Copy the code

A constant string is defined in the global code



static NSString *const AspectsMessagePrefix = @"aspects_";Copy the code

Mark all selectors with this string, prefixed with “aspects”. It then obtains its corresponding AssociatedObject, or creates one if it does not. You end up with a selector prefixed with “Aspects”, corresponding to aspectContainer.

Now that we have the aspectContainer, we can start preparing the information we need to hook the method. This information is stored in AspectIdentifier, so we need to create a new AspectIdentifier.

Call the InstanceType method of AspectIdentifier to create a new AspectIdentifier


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

Things will create the instancetype method, only a failure, it is aspect_isCompatibleBlockSignature method returns NO. Returning NO means that the signature of the method we are replacing, block, and the original method we are replacing, do not match. (This function is explained in detail above and will not be repeated here). After the method signature matches successfully, an AspectIdentifier is created.


[aspectContainer addAspect:identifier withOptions:options];Copy the code

The aspectContainer adds it to the container. With the container and AspectIdentifier initialized, you are ready to hook. Through the beforeAspects options options were added to the container, insteadAspects, afterAspects the three arrays


// Modify the class to allow message interception.
       aspect_prepareClassAndHookSelector(self, selector, error);Copy the code

To summarize what Aspect_Add has done to prepare:

  1. The first call to aspect_performLocked takes advantage of spin locking to keep the operation thread-safe
  2. Then call aspect_isSelectorAllowedAndTrack for incoming parameters calibration, ensure parameters of legitimacy.
  3. AspectsContainer is then created and dynamically added to the NSObject class as an attribute using the AssociatedObject association object.
  4. Create an instance of AspectIdentifier using the selector and option inputs. AspectIdentifier contains specific information about a single Aspect, including the execution time and the specific information needed to execute a block.
  5. The details of the individual AspectIdentifier are then added to the attribute AspectsContainer. Through the beforeAspects options options were added to the container, insteadAspects, afterAspects the three arrays.
  6. The last call hook prepareClassAndHookSelector preparation.

Because the whole article is too long, take a breath, split into 2, see next part.