GitHub can download the sample source code Demo, welcome to like the star, thank you!

In iOS development, we often encounter crashes like this

Instance 0x****** unrecognized selector sent to instance 0x******

In the message mechanism of OC, the receiver of the message cannot find the corresponding selector, so the message forwarding mechanism is started. We can tell the object how to deal with unknown messages in the process of message forwarding through the code, so as to prevent the program from crash.

The default implementation is to throw the following exception, which will crash

Solution Runtime message forwarding mechanism

1, the Runtime

<objc/ Runtime. h> or <objc/message.h> <objc/ Runtime. h> // <objc/runtime.h> //

<objc/message.h> // contains the message mechanism

The compiler will eventually convert the OC code into runtime code, which is compiled by terminal commands. M files:

Clang-rewrite-objc xxx.m you can see the compiled xxx.cpp (C++ file)

2. Message sending and forwarding process can be summarized as follows:

Messaging is the process by which the Runtime quickly finds the IMP via selector. When a function pointer is used, the corresponding method can be implemented.

Message Forwarding (Message Forwarding) is a slow channel that performs a series of Forwarding processes after failing to search for IMP. If it is not forwarded, it will hit logs and throw exceptions.

Message forwarding is divided into three steps as follows:

When a Method resolution object receives an undecipherable message, that is, a Method that cannot be found, it calls the following two methods:

  • (BOOL)resolveClassMethod (SEL) SEL OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); / / class methods
  • (BOOL)resolveInstanceMethod: SEL SEL OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // Instance method

Specific writing method can refer to the following code;

In Runtime, the normal user will import objc/message.h files, which are used for message distribution, but the runtime loads the classes in objc/ Runtime.h files

The Man class inherits from the Person class, and the instance method drinkPear and the class method Smoke are not declared or implemented in either the Man or Person classes

Man. M file

#import “man.h” #import <objc/runtime.h>

@implementation Man

  • (instancetype)init { self = [super init]; if (self) {

// [self performSelector:@selector(sel) withObject:nil]; }

return self;
Copy the code

}

Id dynamicInstanceMethodIMP(id self, SEL _cmd) {NSLog(@” first pragma mark: —-%s: instance method “,FUNCTION); return @”1”; }

// IMP method id dynamicClassMethodIMP(id self, SEL _cmd) {NSLog(@” first pass: —-%s: method “,FUNCTION); return @”2”; }

/* class_addMethod class_addMethod(Class _Nullable CLS, SEL _Nonnull IMP, IMP _Nonnull IMP, Const char * _Nullable types) Class CLS: the name of the Class to which the new method is added (instance method, passed in Class; Class method, passed MetaClss; The plus method in the OC Class is equivalent to the instance method in MetalClas. The Class calls the Class method and the object calls the instance method. Classes are also objects. SEL name: the method name to add IMP IMP: the function that implements this method const char *types: the return value and parameters of the method to add; For example: “v@:@” : v: yes Add method None Return value @ indicates id(that is, the class to be added) : indicates the method type to be added. @ indicates the parameter type */

/** instance method object: when receiving an unreadable message, the first call will be made to the class method of its own class @param sel. The method will be passed into @return

  • ResolveInstanceMethod :(SEL) SEL {if (SEL == @selector(drinkPear)) { Class_addMethod ([self CLass], sel, (IMP)(dynamicInstanceMethodIMP), “@@:”); return YES; } return [super resolveInstanceMethod:sel];

}

/** Class method class: if it is a call to a class method, the class method is fired first

@param sel Method of passing in @return If YES can accept message NO can’t accept message go to step 2 */

  • (BOOL)resolveClassMethod:(SEL) SEL {if (SEL == @selector(smoke)) {// add the method to the metaclass; Replace [self class] or self. Class with object_getClass/objc_getMetaClass (IMP)(dynamicClassMethodIMP); Class methods, Class CLS pass MetaClass class_addMethod(object_getClass(self)/[self getMetaClassWithChildClass:self]/,sel,(IMP)(dynamicClassMethodIMP), “@@:”); //OC: class_getMethodImplementation([self class], @selector(findSmokeMethod)) // class_addMethod(object_getClass(self),@selector(smoke),class_getMethodImplementation([self class], @selector(findSmokeMethod)),”@@:”);

    // Check metaclass [self isMetaClass]; return YES;Copy the code

    } return [super resolveClassMethod:sel];

}

/** check if it is a metaclass */

  • Class c1 = object_getClass(self); /** isMetaClass (); Class c2 = [self getMetaClassWithChildClass:self]; BOOL object_getClass = class_isMetaClass(c1); BOOL objc_getMetaClass = class_isMetaClass(c2); NSLog(@” is object_getClass metaclass: %@”,object_getClass? @”YES”:@”NO”); NSLog(@” is objc_getMetaClass metaclass: %@”,objc_getMetaClass? @”YES”:@”NO”);

}

/** Get the metaclass of the class @param childClass target class @return return metaclass */

  • (Class) getMetaClassWithChildClass (Class) the childClass {/ / string category const char * classChar = [NSStringFromClass (childClass) UTF8String]; Return objc_getMetaClass(classChar);

} implement the following class methods separately (resolveClassMethod reference)

#import “SuperMan. H “#import <objc/runtime.h> // contains operations on classes, member variables, properties, methods

Void eat(id self,SEL SEL){NSLog(@” first forward: method parsing —- class method: eat”); }

@implementation SuperMan

  • (BOOL)resolveClassMethod:(SEL) SEL {/** class: which class to add SEL: which method to add IMP: method implementation => function => entry => function name type: Method type: void to represent v, id parameter to represent @, SEL to represent */

//    Method exchangeM = class_getInstanceMethod([self class], @selector(eatWithPersonName:)); //    class_addMethod([self class], sel, class_getMethodImplementation(self, @selector(eatWithPersonName:)),method_getTypeEncoding(exchangeM));

If (sel == NSSelectorFromString(@"eat")) {// if (sel == NSSelectorFromString(@"eat")) {// if (sel == NSSelectorFromString(@"eat")) {// if (sel == NSSelectorFromString(@"eat")) {// if (sel == NSSelectorFromString(@"eat")); return YES; } else if (sel == NSSelectorFromString(@"writeCode")) {NSLog(@" I'm writing code "); return YES; } return [super resolveClassMethod:sel];Copy the code

}

The @end class_addMethod method can be used to dynamically add a method to a class as described in ios-Runtime class_addMethod

All classes in the OC are essentially objects. The isa of the object points to its own class, the ISA of the class points to the metaclass, the ISA of the metaclass points to the root metaclass, and the ISA of the root metaclass points to itself, thus forming a closed loop. Metaclass methods and whether they are metaclass methods are as follows:

/** check if it is a metaclass */

  • Class c1 = object_getClass(self); /** isMetaClass (); Class c2 = [self getMetaClassWithChildClass:self]; BOOL object_getClass = class_isMetaClass(c1); BOOL objc_getMetaClass = class_isMetaClass(c2); NSLog(@” is object_getClass metaclass: %@”,object_getClass? @”YES”:@”NO”); NSLog(@” is objc_getMetaClass metaclass: %@”,objc_getMetaClass? @”YES”:@”NO”);

}

/** Get the metaclass of the class @param childClass target class @return return metaclass */

  • (Class) getMetaClassWithChildClass (Class) the childClass {/ / string category const char * classChar = [NSStringFromClass (childClass) UTF8String]; Return objc_getMetaClass(classChar);

} Output:

Create the Man object in the MainViewController and call the instance methods drinkPear and the class method Smoke that do not exist in the Man and Person classes

Man *man = [[Man alloc]init]; [man performSelector:@selector(drinkPear)];

// Class method [Man performSelector:@selector(smoke)] cannot be found; The following output is displayed:

MainViewController :(BOOL)resolveInstanceMethod (SEL) Just change the Class name in the class_addMethod method to [Man Class], and change the IMP IMP parameter to class_getMethodImplementation([MainViewController Class], The MainViewController also implements the instance method of forwarding Man

H “#import” mainViewController.h “#import” man.h “#import “SuperMan. H” #import <objc/runtime.h> // Contains operations on classes, member variables, properties, and methods //#import <objc/message.h> contains the message mechanism

@interface MainViewController ()

@end

@implementation MainViewController

  • (void)viewDidLoad { [super viewDidLoad];

    // First view: Method resolution [self FirsForward];

}

#pragma mark – first forward: Method resolution

  • (void)FirsForward { Man *man = [[Man alloc]init]; [man performSelector:@selector(drinkPear)];

    // Class method [Man performSelector:@selector(smoke)] cannot be found;

    SuperMan * SuperMan = [SuperMan alloc] init]; SEL select = NSSelectorFromString(@”eat”); [SuperMan resolveClassMethod:select]; [superMan performSelector:@selector(eat)];

}

  • (void)findDrinkPearMethod {NSLog(@” instance method: Man drinkPear”);

}

// Instance method

  • (BOOL)resolveInstanceMethod:(SEL)sel { if ([super resolveInstanceMethod:sel]) { return YES; }else {//IMP IMP class_getMethodImplementation class_addMethod([Man class],@selector(drinkPear),class_getMethodImplementation([MainViewController class], @selector(findDrinkPearMethod)),”v@:”); return YES; }

}

@end output result: MainViewController resolveInstanceMethod MainViewController resolveInstanceMethod MainViewController Then the second and third forwarding will not go.

Instance methods resolveInstanceMethod can be implemented in both MainViewController and Man, but class methods can only be implemented in Man

When Man receives a message about drinkPear (instance method) and Smoke (class method), the instance method will first call resolveInstanceMethod: and the class method will call resolveClassMethod:. The unknown message is then resolved by dynamically adding an implementation of drinkPear (instance method) and Smoke (class method) via the class_addMethod method, at which point the message forwarding process ends prematurely. However, when Man does not receive the drinkPear (instance method) and Smoke (class method) unknown messages, the first step returns NO, which means a second forward is invoked if there is NO dynamically added implementation method

2. Second forwarding: fast forwarding (message redirection) (the second and third stages are processed for objects, without considering class methods)

If the realization of the forwarding method has not been found for the first time, you will call the following methods: – (id) forwardingTargetForSelector aSelector: (SEL)

  • (id) forwardingTargetForSelector: (SEL) aSelector OBJC_AVAILABLE (10.5, 2.0, 9.0, 1.0, 2.0);

In this step, the message is forwarded to the other object replacement receiver; Change the call object for that Selector

If we can find an object to implement the called study method, we can return the object in this method and it will execute the study method as follows:

#pragma mark – Second forwarding: Fast forwarding

  • (void)SecondForward { Man *man = [[Man alloc]init]; [man performSelector:@selector(study)];

} Man. M file

#pragma mark – Second forwarding: Fast forwarding

  • (id) forwardingTargetForSelector (SEL) aSelector {NSLog (@ “second forward: fast forward – forwardingTargetForSelector: %@”, NSStringFromSelector(aSelector)); Son *son = [[Son alloc] init]; if ([son respondsToSelector: aSelector]) { return son; } return [super forwardingTargetForSelector: aSelector];

} The Son class is found to call its study method

Son.h file @interface Son: NSObject

/ / learning

  • (void)study;

@end

Son.m file @implementation Son

/ / learning

  • (void)study {NSLog(@” I want to good good study, day day up!” );

}

@end output is as follows:

Normal forwarding if the second forwarding fails to find an object that can handle the method, the following method is called:

  • (void)forwardInvocation:(NSInvocation *)anInvocation;

  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

  • (void)doesNotRecognizeSelector:(SEL)aSelector;

Message forwarding also changes the calling object so that the message is invoked on a new object; The difference is that the forwardInvocation method has an NSInvocation object that stores all the information for the method invocation, including SEL, parameter and return value description, etc. JSPatch is based on message forwarding

When the Man class receives a message with code, it cannot process the first two steps and goes to the third step:

This is the Man class

  • (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector method is invoked, this method returns a code method signature,

Man class if the method signature of code is returned

  • (void)forwardInvocation:(NSInvocation *) the anInvocation method is called, in which the code method we call is handled,

DoesNotRecognizeSelector method is executed, causing an unrecognized selector sent to instance to crash.

Pragma mark – Third forwarding: Normal forwarding

  • (void)ThirdForward { Man *man = [[Man alloc]init]; [man performSelector:@selector(code)]; Man performSelector:@selector(missMethod)];

} Man. M file

// Returns the signature of the SEL method, which is encapsulated according to the parameters of the method

  • (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector {NSLog (@ “third forwarding: conventional forward — method signature for the selector: %@”, NSStringFromSelector(aSelector)); if (aSelector == @selector(code)) { return [NSMethodSignature signatureWithObjCTypes:”V@:@”]; } return [super methodSignatureForSelector:aSelector];

}

// Get the method signature, and process (create an alternate object response passed in to wait for the response SEL)

  • (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@”forwardInvocation: %@”, NSStringFromSelector([anInvocation selector])); If ([anInvocation Selector] == @selector(code)) {CodeMan * CodeMan = [[CodeMan alloc] init]; // Standby object invocation invokeWithTarget:codeMan [anInvocation invokeWithTarget:codeMan]; }

}

// Throw an exception if the standby object cannot respond

  • (void)doesNotRecognizeSelector:(SEL)aSelector { NSLog(@”doesNotRecognizeSelector: %@”, NSStringFromSelector(aSelector)); [super doesNotRecognizeSelector:aSelector];

} missMethod method can not handle, go doesNotRecognizeSelector method, as follows

The CodeMan class is found and its code method is called

CodeMan.h @interface CodeMan : NSObject

/ / code

  • (void)code;

@end

CodeMan.m @implementation CodeMan

/ / code

  • (void)code {NSLog(@” I want to learn to program!” );

}

@end output is as follows:

At this point, the code message has been processed by the codeMan instance

In each of the three forward phases, the message recipient has an opportunity to process the message. Processing becomes more expensive later in the process, and it is best to process the message in the first stage so that the Runtime caches the result after processing and sends the same message again to improve processing efficiency. It is also less costly for the receiver to transfer the message in the second phase than to proceed to the forwardInvocation, and if the final step is the forwardInvocation, the full NSInvocation object needs to be handled.

At this point, the complete message forwarding process is completed. The complete message forwarding process is as follows:

The principles and practical uses of the iOS Runtime message forwarding mechanism are selected

1, JSPatch –iOS dynamic update scheme specific implementation bang God has carried out a detailed explanation in the following two blogs, very subtle use of the message forwarding mechanism to JS and OC interaction, so as to achieve iOS hot update. Although apple’s hot update in 2017 made the approval rate of JSPatch impossible to pass for a while, it was basically able to pass after the source code was obfuscated by Bang God. Either way, this dynamic approach is a technological advance, but one that apple’s father is currently suppressing. However, if you don’t mess with the normal obfuscated version on the Bang God platform, the pass rate is still ok. If you are interested, you can take a look at these two principles, and only the parts that use message forwarding are selected here.

blog.cnbang.net/tech/2808/ blog.cnbang.net/tech/2855/

 

The detailed implementation principle can go to the bang god blog to check.

2. At @dynamic, the @synthesize automatically generates getters and setters for @property, whereas at @dynamic tells the compiler, You don’t have to generate getter and setter methods. When using @dynamic, we can use the message forwarding mechanism to dynamically add getters and setters. Of course you can do it in other ways.

3, the realization of multiple proxy using message forwarding mechanism can be implemented without code intrusion of multiple proxy, so that different objects can simultaneously proxy the same callback, and then in their respective responsible area for the corresponding processing, reducing the coupling of the code.

Blog.csdn.net/kingjxust/a…

4, indirect realization of multiple inheritance in Objective – C itself does not support multiple inheritance, this is because the mechanism of message name lookup happens in operation and sometimes not at compile time, it is difficult to solve the multiple base classes can lead to ambiguity problem, but you can create multiple functions through message forwarding mechanism in the internal object, the function is not possible to forward to other objects, This creates the illusion of multiple inheritance. Forwarding is similar to inheritance and can be used to add some multi-inheritance effects to OC programming, with one object forwarding messages as if it were taking over or “inheriting” another object. Message forwarding compensates for objC’s lack of support for multiple inheritance and avoids the bloated complexity of a single class caused by multiple inheritance.

 

GitHub can download the sample source code Demo, welcome to like the star, thank you!

Reference article:

Mechanism and usage of the iOS Runtime message forwarding mechanism

IOS understand message forwarding mechanism in Objective-C with Demo

IOS message forwarding mechanism and solutions to avoid crashes Little ants eating rice

— — — — — — — —

Copyright notice: This article is originally published BY CSDN blogger “MinggeQingchun”, in accordance with CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement. The original link: blog.csdn.net/MinggeQingc…