The essence of a method

1. Through the compilation of CLang into CPP file, you can see the underlying code and get the essence of the method

Compatible compilation (less code) : clang-rewrite-objc main.m-o main.cpp

Xcrun-sdk iphoneos clang-arch arm64-rewriteobjc main.m -o main.cpp

2. Code conversion

Person *p = [Person alloc];
[p fly];
Copy the code
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("fly"));
Copy the code

((Person *(*)(id, SEL))(void *)) is type intensive

(ID)objc_getClass(“Person”) Gets the Person class object

Sel_registerName (“alloc”) is the same as @selector()

Finally, it can be abbreviated as ((type hardening)objc_msgSend)(object, method call);

3. The nature of the method

An object method like this [obj foo], the compiler turns it into a message to send objc_msgSend(obj, foo), so the essence of the method is to send a message through objc_msgSend

id objc_msgSend(id self, SEL op, …) ; Id self is the receiver of the message, SEL op is the method name of the message, c string… Is the argument list

Two things at oncemsg_sendSend a message

  1. First define instance methods for Father and class methods for Son
#import <Foundation/Foundation.h>
#import <objc/message.h>

@interface Father: NSObject
- (void)walk;
+ (void)run;
@end

@implementation Father
- (void)walk { NSLog(@"%s",__func__); }
+ (void)run { NSLog(@"%s",__func__); }
@end

@interface Son: Father
- (void)jump;
+ (void)swim;
@end
Copy the code
  1. Msg_send Sends a message
Son *s = [Son new]; objc_msgSend(s, sel_registerName("jump")); Objc_msgSend (objc_getClass("Son"), sel_registerName("swim")); 3. Send instance methods to the parent class (receiver -- instance object; Struct objc_super superInstanceMethod; superInstanceMethod.receiver = s; superInstanceMethod.super_class = objc_getClass("Father"); objc_msgSendSuper(&superInstanceMethod, sel_registerName("walk")); 3. Send class methods to the parent class (receiver -- class object; Struct objc_super superClassMethod; struct objc_super superClassMethod; superClassMethod.receiver = [s class]; superClassMethod.super_class = class_getSuperclass(object_getClass([s class])); objc_msgSendSuper(&superClassMethod, sel_registerName("run"));Copy the code

If there are Too many arguments to function call, expected 0, have 2, go to BuildSetting and change the configuration to the following figure

Three Message sending process

The core processes

How the objc_msgSend function internally sends messages to objects (message sending process, method calling process)

  1. If you go to objc _msgSend, the system will first check if the receiver is nil. If nil returns, the method will be called and the program will not crash.

  2. If it’s not nil, then we find the class that the object belongs to based on the object’s ISA pointer, and we go to the method cache of that class and we look for the method in the cache, and the method cache is done with a hash table, so it’s very efficient, and if we find it, we call it, and if we don’t find it, we go to the methods list of that class, Here to have a list of sequence method using binary search, to row a sequence traversal search list of the methods is used, if find a method in the class list of the methods, the first put the method cache into the cache of the current class, and then call the method, if not found, will find it according to the current class’s superclass pointer of the parent class, Look it up in the parent class.

  3. If it is found, it will first look for the method in the method cache of the parent class. If it is found, it will first look for the method in the cache of the current class. If it is not found, it will look for the method in the methods list of the parent class. If a method is found in the method list of the parent class, it is first cached in the cache of the current class (note that it is not in the cache of the parent class), then the method is called, and if it is not found, it goes up layer by layer, all the way to the root class, all the way to nil.

  4. If it gets to nil, and it still doesn’t find the method, it triggers dynamic method resolution.

The flow chart

Four Message forwarding process

If a method list does not find a corresponding selector, the system gives you three chances to remedy it.

For the first time,

+ (BOOL)resolveInstanceMethod (SEL) SEL {} (instance method) + (BOOL)resolveClassMethod (SEL) SEL {} (class method)Copy the code

In the resolveInstanceMethod: method, use class_addMethod to bind the unimplemented myTestPrint: to the myMethod, and return YES.

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(myTestPrint:)) {
        class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
        return YES;
    }else {
        return [super resolveInstanceMethod:sel];
    }
}

void myMethod(id self, SEL _cmd,NSString *nub) {
	NSLog(@"ifelseboyxx%@",nub);
}

Copy the code

The second time

- (id)forwardingTargetForSelector:(SEL)aSelector {}
Copy the code

This method requires an ID to be returned. The usage scenario is to forward some method of class A to an implementation of class B.

/ / Son. M - (id) in forwardingTargetForSelector: (SEL) aSelector {# pragma clang diagnostic push # pragma clang diagnostic ignored "-Wundeclared-selector" if (aSelector == @selector(myTestPrint:)) { #pragma clang diagnostic pop return [Person new]; }else{ return [super forwardingTargetForSelector:aSelector]; } //Person. M @interface Person: NSObject @end @implementation Person - (void)myTestPrint:(NSString *)str { NSLog(@"ifelseboyxx%@",str); } @endCopy the code

The third time

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}
Copy the code

The first requires that a method signature be returned, and the second requires that the implementation be forwarded. The two are interdependent; the second method is executed only if the correct method signature is returned. The function of this forward is similar to the second comparison, which forwards A method of class A to the implementation of class B. Different is the third time, forward relative to the second more flexible, forwardingTargetForSelector: fixed forwarded to only one object; ForwardInvocation: Allows us to forward to multiple objects.

/ / Son. In the m - (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector {if (aSelector = = @ the selector (myTestPrint:)) { return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { Person *person = [Person new]; Animal *animal = [Animal new]; if ([person respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:person]; } if ([animal respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:animal]; }}Copy the code
@interface Person : NSObject
@end

@implementation Person
- (void)myTestPrint:(NSString *)str {
	NSLog(@"ifelseboyxx%@",str);
}
@end
Copy the code
@interface Animal : NSObject
@end

@implementation Animal
- (void)myTestPrint:(NSString *)str {
	NSLog(@"tiger%@",str);
}
@end
Copy the code

⚠️ If the implementation is not found on the third chance, crash:

unrecognized selector sent to instance 0x7f9f817072b0
Copy the code

The flow chart

The whole message is sent and forwarded