What are the ##Aspects used for?
Aspect is a succinct and efficient framework for enabling iOS to support AOP(Aspect oriented programming). You can use Aspect to insert a piece of code into a method of each class or instance of a class. The pointcut can be before (executed before the original method) /instead (executed after the original method).
Think of Aspects as method swizzling on steroids. It allows you to add code to existing methods per class or per instance, whilst thinking of the insertion point e.g. before/instead/after. Aspects automatically deals with calling super and is easier to use than regular method swizzling.
- This blog post is based on v1.4.2 version of the source code analysis.
The technical provisioning Aspect is built on top of the Runtime. Before studying Aspect, you need to understand the following concepts: NSMethodSignature and NSInvocation Can be used not only for method invocation, but also for block invocation. In the Aspect, it is using NSMethodSignature, NSInvocation realized the unification of block processing. It doesn’t matter if you don’t know how NSMethodSignature and NSInvocation are used and how to use them to execute method or block.
#### Object call method code examples An instance object can call its methods in three ways.
- (void)test{
//type1
[self printStr1:@"hello world 1"];
//type2
[self performSelector:@selector(printStr1:) withObject:@"hello world 2"];
//type3 / / get the method signature NSMethodSignature * sigOfPrintStr = [self methodSignatureForSelector: @ the selector (printStr1:)]; / / get the method signature corresponding invocation NSInvocation * invocationOfPrintStr = [NSInvocation invocationWithMethodSignature: sigOfPrintStr]; /** Sets the message receiver, with [invocationOfPrintStrsetArgument:(__bridge void * _Nonnull)(self) atIndex:0] Equivalent */ [invocationOfPrintStrsetTarget:self]; /** Sets the selector to perform. With [invocationOfPrintStrsetArgument:@selector(printStr1: atIndex:1] Equivalent */ [invocationOfPrintStrsetSelector:@selector(printStr1:)]; // Set the parameter NSString * STR = @"hello world 3";
[invocationOfPrintStr setArgument:&str atIndex:2]; // Invoke [invocationOfPrintStr]; } - (void)printStr1:(NSString*)str{
NSLog(@"printStr1 %@",str);
}
Copy the code
When the test method is called, it prints:
The 2017-01-11 15:20:21. 642 AspectTest (2997-146594)printStr1 hello world 1
2017-01-11 15:20:21.643 AspectTest[2997:146594] printStr1 hello world 2
2017-01-11 15:20:21.643 AspectTest[2997:146594] printStr1 hello world 3
Copy the code
Type1 and type2 are the ones we use, so I won’t repeat them here, but let’s talk about type3. NSMethodSignature and NSInvocation is a method invocation provided by the Foundation framework and is often used for message forwarding.
# # # # NSMethodSignature overview
NSMethodSignature describes the type information of a method: the return value type, and the type of each parameter. You can create it as follows:
@ interface NSObject / / for instance method signature - (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector; / / class methods for signature + (NSMethodSignature *) instanceMethodSignatureForSelector (SEL) aSelector; @end ------------- // Create method signature @interface NSMethodSignature + (nullable NSMethodSignature) using ObjCTypes *)signatureWithObjCTypes:(const char *)types; @endCopy the code
Creating an NSMethodSignature using the instance and class methods of NSObject is easy. Check out signatureWithObjCTypes. In OC, each data type can be represented by a character encoding (Objective-C type encodings). For example, the character ‘@’ represents an object and ‘I’ represents an int. An array of these characters can then represent the method type. For example: the above printStr1: corresponds to ObjCTypes v@:@.
- ‘v’ : void, the first character represents the return value type
- ‘@’ : an object of type ID, the first parameter type
- ‘: corresponds to SEL, the second parameter type
- ‘@’ : an object of type ID, the third parameter type, i.e
- (void)printStr1:(NSString*)str
The STR.
PrintStr1: ObjCTypes is a single argument. To understand this you must also understand the messaging mechanism in OC. The corresponding structure of a method is as follows. The arguments in ObjCTypes are identical to the arguments to the IMP method_IMP function pointer. There are a lot of related content, do not understand this article can refer to the method and information.
typedef struct objc_method *Method; struct objc_method { SEL method_name OBJC2_UNAVAILABLE; // Method name char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; // method implementation}Copy the code
# # # # NSInvocation overview
As shown in the example code, we can use + (NSInvocation *) invocationWithMethodSignature (sig NSMethodSignature *); Create the NSInvocation object. Next you set the parameters and call Invoke. – (void)getReturnValue:(void *)retLoc; Gets the return value. Note that the number and type of parameters set on the NSInvocation object and the type of returned value must be the same as the NSMethodSignature object used when the object is created, otherwise cresh.
#### Using NSInvocation block The following shows two ways to invoke block
- (void)test{
void (^block1)(int) = ^(int a){
NSLog(@"block1 %d",a);
};
//type1
block1(1);
//type2 // Obtain the method signature corresponding to the block type. NSMethodSignature *signature = aspect_blockMethodSignature(block1); NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; [invocationsetTarget:block1];
int a=2;
[invocation setArgument:&a atIndex:1];
[invocation invoke];
}
Copy the code
Type1 is the usual method and will not be described again. Take a look at type2. Type2 is the same as the method invocation above, except that the first argument to the NSInvocation object generated by block is the block itself, and the rest is the block itself.
Since the system does not provide an API to get the block’s ObjCTypes, we have to find the ObjCTypes to generate the NSMethodSignature object! ObjCTypes OC is a dynamic language that can be converted to C by compiling OC. The compiled data structure corresponding to a block is a struct. (Technical points in block still survive, recommend the book “Advanced Programming in Objective-C”)
// Block internals.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
In this structure, the const char *signature field is what we want. Get signature and create an NSMethodSignature object using the following 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
Swizzling calls a method in Objective-C that sends a message to an object. Each class has a list of methods that hold the name of the selector and the mapping between the method implementation. IMP is a bit like a function pointer that points to a specific implementation of Method.
The specific implementation code is as follows:
Code source: https://github.com/hejunm/iOS-Tools @implementation HJMSwizzleTools:NSObject + (void)hjm_swizzleWithClass:(Class)processedClass originalSelector:(SEL)originSelector swizzleSelector:(SEL)swizzlSelector{ Method originMethod = class_getInstanceMethod(processedClass, originSelector); Method swizzleMethod = class_getInstanceMethod(processedClass, swizzlSelector); DidAddMethod returns when processedClass implements originSelectorfalseOtherwise returns true. If the current class doesn't implement originSelector and the parent class does implement originSelector, that's just using method_exchangeImplementations will swizzle originSelector for the parent class. This can be very problematic. BOOL didAddMethod = class_addMethod(processedClass, originSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));if (didAddMethod) {
class_replaceMethod(processedClass, swizzlSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}else{
method_exchangeImplementations(originMethod, swizzleMethod);
}
}
@end
Copy the code
You can use it like this
+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [HJMSwizzleTools hjm_swizzleWithClass:self originalSelector:@selector(viewDidLoad) swizzleSelector:@selector(swizzleViewDidLoad)]; }); } // has been replaced. - (void)viewDidLoad { [super viewDidLoad]; } // Now the system will call this method - (void)swizzleViewDidLoad {NSLog(@)"do something");
}
Copy the code
###3. The message forwarding process calls a method in Objective-C that sends a message to an object. If there is no corresponding implementation for the message, the message is forwarded. The forwarding flow chart is as follows:
The following code to demonstrate
- resolveInstanceMethod
This method is called first when no corresponding method is found according to selector, where you can add a method to a class. And return yes. The following code only declares the runTo method and does not implement it.
//Car.h
@interface Car : NSObject
- (void)runTo:(NSString *)place;
@end
//Car.m
@implementation Car
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(runTo:)) {
class_addMethod(self, sel, (IMP)dynamicMethodIMPRunTo, "v@:@");
return YES;
}
return[super resolveInstanceMethod:sel]; } static void dynamicMethodIMPRunTo(id self, SEL _cmd,id place){NSLog(@selector(runTo:)"dynamicMethodIMPRunTo %@",place);
}
@end
Copy the code
- forwardingTargetForSelector
If there is No implementation resolveInstanceMethod, return No, or No dynamic adding methods, will be executed forwardingTargetForSelector. So in this case you can return an object that performs this selector, otherTarget, and then the message will be resent to that otherTarget.
//Person.h @interface Person : NSObject - (void)runTo:(NSString *)place; @end //Person.m @implementation Person - (void)runTo:(NSString *)place; { NSLog(@"person runTo %@",place); } @end //Car.h @interface Car : NSObject - (void)runTo:(NSString *)place; @ the end / / Car. M @ implementation Car - (id) forwardingTargetForSelector aSelector: (SEL) {/ / the message is forwarded to the Person of the instanceif (aSelector == @selector(runTo:)){
return [[Person alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
Copy the code
- forwardInvocation
If the above two situations are not executed, message forwarding via forwardInvocation is executed.
@ implementation Car - (NSMethodSignature *) methodSignatureForSelector aSelector: (SEL) {/ / whether the selector to need to forward, If so, manually generate the method signature and return it.if (aSelector == @selector(runTo:)){
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return[super forwardingTargetForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation{// determine whether the anInvocation to be handled is our anInvocationif (anInvocation.selector == @selector(runTo:)){
}else{
}
}
@end
Copy the code
The NSInvocation object holds all the information we need to invoke a method. Take a look at its properties and methods:
methodSignature
Contains the type of return value, the number of parameters, and the type of each parameter.- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
Gets the argument passed when method is called- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
Set the index parameter.- (void)invoke;
Start to perform- (void)getReturnValue:(void *)retLoc;
Get the return value
The following code shows how to get the values of each parameter passed when calling method
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if (anInvocation.selector == @selector(runTo:)){
void *argBuf = NULL;
NSUInteger numberOfArguments = anInvocation.methodSignature.numberOfArguments;
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [anInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
if(! (argBuf = reallocf(argBuf, argSize))) { NSLog(@"Failed to allocate memory for block invocation.");
return; } [anInvocation getArgument:argBuf atIndex:idx]; // argBuf now holds the value of the index argument. You can use these values for other things, such as assigning values to arguments in a block and calling. }}else{}}Copy the code
#### manually triggers message forwarding (method already implemented) The message forwarding described above is automatically performed when there is no corresponding implementation of selector, which is called automatic message forwarding. Now there is a requirement: even if the Car class implements runTo:, execute [objOfCar runTo:@”shangHai”]; Message forwarding (manually triggered), how to achieve? To do this, use method swizzling to change the implementation of selector to _objc_msgForward or _objc_msgForward_stret. When you call selector, the message is forwarded. Look at the following code:
@implementation Car // implementation method swizzling + (void)load{SEL selector = @selector(runTo:); Method targetMethod = class_getInstanceMethod(self.class, @selector(selector)); const char *typeEncoding = method_getTypeEncoding(targetMethod);
IMP targetMethodIMP = _objc_msgForward;
class_replaceMethod(self.class, selector, targetMethodIMP, typeEncoding);
}
- (void)runTo:(NSString *)place{
NSLog(@"car runTo %@",place); } // Message forwarding, call this method. - (void)forwardInvocation:(NSInvocation *)anInvocation{if (anInvocation.selector == @selector(runTo:)){
}else{}}Copy the code
If _objc_msgForward or _objc_msgForward_stret is mentioned above, how to choose? First, both are message forwarding, which goes something like this: _objc_msgForward_stret if the return value of the forwarded message is a struct, _objc_msgForward otherwise. Resources. Simply quote the JSPatch author’s explanation
In the case of obj_msgSend, the first two arguments are always self / _cmd. They are placed in the register. The return value is also stored in the register after the last execution. A normal return value (int/pointer) is too small to fit in a register, but some structs are too big to fit in a register, so you have to use another way: allocate a bit of memory at the beginning, store the pointer in the register, and write the return value to the memory that the pointer points to. So the register has to make room for this pointer, and self / _cmd changes its position in the register. Objc_msgSend doesn’t know that the position of self / _cmd has changed, so use another method objc_msgSend_stret instead. That’s roughly how it works. A special struct is typed on the NSMethodSignature’s debugDescription and can only be determined by the string. So the final processing, in non-ARM64, is _objc_msgForward_stret if it’s a special struct, and _objc_msgForward if it’s not.
Get _objc_msgForward or _objc_msgForward_stret based on the selector return value type:
// this code comes from Aspect static IMP aspect_getMsforwardimp (NSObject *self, SEL selector) {IMP msgForwardIMP = _objc_msgForward;#if ! defined(__arm64__)
Method method = class_getInstanceMethod(self.class, selector);
const char *encoding = method_getTypeEncoding(method);
BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
if (methodReturnsStructValue) {
@try {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(encoding, &valueSize, NULL);
if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
methodReturnsStructValue = NO;
}
} @catch (__unused NSException *e) {}
}
if (methodReturnsStructValue) {
msgForwardIMP = (IMP)_objc_msgForward_stret;
}
#endif
return msgForwardIMP;
}
Copy the code
##Aspects source code has been thinking about how to use words to clearly describe the implementation of Aspects, finally decided to use the form of annotations on the source code. Be lazy. Source code analysis of Aspects
##Aspects usage scenarios
- The app is buried point
Aspects
A typical application of the framework that you can passRunTime Application Examples – Thinking about buried pointsTo understandAspects
The usage scenario of.