I. Introduction and application

1.1 objc_msgSend

Calling a method in Objective-C is called messaging, and a message has a name or selector, can receive arguments, and possibly return values. Objc_msgSend is basically a function implementation of message passing in the underlying C language. In Objective-C, most method calls are implemented through objc_msgSend. Of course, except for special cases such as load method ·. In general, the following message is sent to the object:

Id retrunValue = [someObject messageName:parameter];Copy the code

SomeObject is the message receiver, messageName is the selector, and the selector and parameter together are called messages. At the bottom level, the compiler receives it and converts it to the obj_msgSend function, which is declared as follows:

void objc_msgSend(void /* id self, SEL op, ... * / )
Copy the code
  • The first parameter is the message receiver
  • The second parameter is SEL

After the above is sent to the object, the conversion will be:

id retrunValue = objc_msgSend(someObject, @selector(messageName:), parameter);
Copy the code

Objc_msgSend invokes the appropriate method based on the recipient and selector types.

1.2 applications

Let’s see how it is called by calling the objc_msgSend method directly.

        NSMutableArray *array = [[NSMutableArray alloc] init];
        [array addObject:@"dog"];
        NSInteger index = [array indexOfObject:@"dog"];
        NSString *last = [array lastObject];
        [array removeLastObject];
Copy the code

Change the above method to objc_msgSend and call as follows:

NSMutableArray *array = ( (NSMutableArray * (*) (id, SEL)) objc_msgSend) ( (id) [NSMutableArray class].@selector(alloc) );
        array = ( (NSMutableArray * (*) (id, SEL)) objc_msgSend) ( (id)array, @selector(init)); ((void (*) (id, SEL, NSString *)) objc_msgSend) ( (id)array, @selector(addObject:), @"dog");
NSInteger index = ( (NSInteger (*) (id, SEL, NSString *)) objc_msgSend) ( (id)array, @selector(indexOfObject:), @"dog");
NSString *last = ( (NSString * (*) (id, SEL)) objc_msgSend) ( (id)array, @selector(lastObject)); ((void (*) (id, SEL)) objc_msgSend) ( (id)array, @selector(removeLastObject));
Copy the code

The code up here.

1.3 Why objc_msgSend

In C, function calls are implemented using static binding, which determines at compile time what functions should be called at run time. Look at an example:

#import <stdio.h>
void printHello(a) {
    printf("Hello world! \n");	
}

void printGoodbye(a) {
  printf("Good bye! \n");	
}

void doTheThing(int type) {
   if(type == 0){
      printHello();
   }else{
      printGoodbye();
   }
   return 0;
}
Copy the code

Without inlining, the compiler already knows that printHello and printGoodbye are in the program when it compiles the code, and generates instructions to call them directly. The function address is actually hard-coded into the instruction. If you change the code to the following:

#import <stdio.h>
void printHello(a) {
    printf("Hello world! \n");	
}

void printGoodbye(a) {
    printf("Good bye! \n");	
}

void doTheThing(int type) {
    void (*fnc)();
    if(type == 0){
        fnc = printHello;
    }else{
        fnc = printGoodbye;
    }
    fnc();
    return 0;
}
Copy the code

This is where dynamic binding is used, because the function to be called is not determined until run time. The compiler generates different instructions in this case. In the first example, both if and else have function call instructions. In the second example, there is only one function call instruction, but the address of the function to be called cannot be hard-coded in the instruction, but is read at run time. In Objective-C, if you want to send a message to an object, you use a dynamic binding mechanism to determine which method to call. At the bottom, all methods are plain C functions, but which method an object calls once it receives a message is determined at runtime, and can even be changed at runtime. Objc_msgSend is a function that supports dynamic binding in Objective-C.

1.4 moreobjc_msgSendfunction

There are several similar methods to the objc_msgSend function found in the

header:

//Sends a message with a data-structure return value to an instance of a class.
void objc_msgSend_stret(id self, SEL op, ...)
double objc_msgSend_fpret(id self, SEL op, ...)

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
Copy the code

With regard to the above three functions, a paragraph is extracted without empirical demonstration:

Objc_msgSend_stret: If a message to be sent returns a structure, it can be handled by this function. This function can process the message only if the CPU’s registers are large enough to hold the return type. If the returned value cannot fit in the CPU register (for example, the returned structure is too large), then another function performs the distribution. At this point, the function processes the structure returned by the message by assigning a variable on the stack.

Objc_msgSend_fpret: If the message returns a floating point number, it can be handled by this function. Some architectural cpus require special handling of the “floating-point register” when calling a function, meaning that the usual objc_msgSend is not appropriate in this case. This function is designed to deal with some somewhat surprising strange conditions in architectural cpus such as x86.

Objc_msgSendSuper: If you want to send a message to a superclass, such as [super message:parameter], then this function is used. There are also two other functions equivalent to objc_msgSend_stret and objc_msgSend_fpret that handle the corresponding messages to super.

Second, the discoveryobjc_msgSend

2.1 Phased process

2.2 Source Guide

Through the process of compilation, the process is sorted out:

Next, enter the source code:

The last step: _objc_msgForward_impcache is again a compilation, but has been decompiled by a master. Out of Hmmm, What’s that Selector? C) forwarding. D) forwarding.

3. Message sending

void objc_msgSend(id receiver, @selector(),...).Copy the code

The first step in the process of sending a message is to send a message, which looks for corresponding methods in the _ method cache and method list of the class and its superclass. First, it looks for the class “method list” according to the class to which the message receiver belongs. If it can find one that matches the name of the “selector child”, it jumps to its implementation code. Otherwise, the recipient’s inheritance system continues to look up, waiting for the appropriate method to jump. If not, the process of dynamic method resolution is performed. Because of the performance penalty of the above procedure, objc_msgSend will cache the method and its jump address in the hash table after the recipient looks up the method for the first time. Every class has such a cache, and then sends the message, the hash table is searched first and the jump is achieved quickly. Of course, this is slower than static binding. In practice, however, this does not create a performance bottleneck for your application. If it’s really a bottleneck, you can just write pure C functions. The objC source code summarizes the following flow:

A few points need to be made about the above process.

3.1 Search in the method list

To find methods in the method list, the system makes the following distinctions in order to improve efficiency:

  • List of sorted methods: binary lookup
  • List of unsorted methods: traversal
// Find a list of methods for a class or a class ———— one-dimensional array
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        // Sorted: binary search
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Unsorted: linear traversal finding method
        for (auto& meth : *mlist) {
            if (meth.name == sel) return&meth; }}return nil;
}
Copy the code

3.2 Search in the list of parent methods

If the method is found in the parent class method list, it is cached in the current receiverClass.

   // Superclass cache. Search from the Superclass method cache
    imp = cache_getImp(curClass, sel);
    if (imp) {
        // Find the method in the parent cache
        if(imp ! = (IMP)_objc_msgForward_impcache) {// Found the method in a superclass. Cache it in this class.
            // Cache the method in the parent class into its own class
            log_and_fill_cache(cls, imp, sel, inst, curClass);
            gotodone; }}Copy the code

Dynamic method analysis

If no method is found during message delivery, dynamic method resolution is entered. Dynamic method resolution is the temporary addition of a method implementation at run time to handle message processing. The function for adding methods is:

/* CLS: the object to which a method needs to be added name: selector method name IMP: the corresponding function implements types: the encoding corresponding to the function */
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types)
Copy the code

Accordingly, the following is a flow of dynamic parsing:

Sample code corresponding to dynamic method resolution.

4.1 Object methods and class methods

Dynamic method resolution, which can be added to handle object methods or class methods. But notice the difference between a class method, which needs to add methods to its metaclass object, and an instance object, which needs to add methods to its class object. And that makes sense because:

  • Call the object method, find the method is to go to the class object method list;
  • To call a class method, go to the list of metaclass methods;

Here is an example:

void notFound_eat(id self, SEL _cmd)
{
    NSLog(@ % @ - % @ "".self.NSStringFromSelector(_cmd));
    NSLog(@"current in method %s", __func__);
}

// Object method resolution
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        // Notice the addition to self, in this case the class object
        class_addMethod(self, sel, (IMP)notFound_eat, "v16@0:8");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

// Class method resolution
+ (BOOL)resolveClassMethod:(SEL)sel
{
    if (sel == @selector(learn)) {
        // The first argument is object_getClass(self).
        class_addMethod(object_getClass(self), sel, (IMP)notFound_learn, "v16@0:8");
        return YES;
    }
    return [super resolveClassMethod:sel];
}
Copy the code

4.2  class_addMethod

The following isclass_addMethodThere are two ways to add:

4.3 Mark “Dynamically resolved”

What does this mark do? After dynamic parsing, the message is sent again. The reason for this flag is to break: ** message send -> dynamic method resolution -> Message send -> dynamic method resolution…. ** this infinite loop. It is executed only once: Message send -> Dynamic method resolution -> Message Send -> Message forward.

4.4  @dynamicThe implementation of the

One of the best practical use cases for dynamic method resolution is the implementation of @dynamic. @dynamic is an implementation that tells the compiler not to automatically generate getters and setters and to add method implementations at run time

V. Message forwarding

In message sending – not found in the cache and method list, nor added in dynamic method resolution. This leads to the message forwarding process. Message forwarding process, classified in two steps:

  1. Find backup receivers
  2. Complete message forwarding

Here is the flow chart:

5.1 Standby Receiver

Backup receiver: clear meaning, equivalent to “THIS message, I do not want to receive, there is a backup object to receive.” The standby receiver is implemented in the following method:

- (id)forwardingTargetForSelector:(SEL)aSelector;
Copy the code

At this point, the run-time system asks it if it wants to forward the message to another receiver for processing. Returns the backup object if the method can find it, nil otherwise. Through this scheme, some characteristics of multiple inheritance can be simulated by composition. Inside an object, there may be a series of other objects that can be returned by this method to related internal objects that can handle a selector, so that it looks externally as if the object itself is handling the messages. It is important to note that this step does not change the content of the message. If this is to be done, the full message forwarding mechanism must be used.

Example code -03 message forward – standby receiver

5.2. Complete message forwarding

In the absence of a backup receiver, the full message forwarding process is entered. Complete message forwarding is also divided into two steps:

  1. Get method signature
  2. For forwarding

Example code –03 message forwarding – instance method

5.2.1 Method signature

Method signature, which can be obtained as follows

5.2.2 forwardInvocation

forwardInvocationMethod, which is very powerful and highly customizable, gives it great privileges.NSInvocationIs a class that encapsulates a method call, enclosing all the details related to the message not yet processed. This object containsSelect the son,Targetandparameter. In the triggerNSInvocationObject,Message distribution systemMessages are assigned to the target object. Of course, this method can also forward the message directlyBackup receiver, but that would have been done in the previous step, so generally at this point, the message content is modified to do what only it can do.But care needs to be taken when usingNSInvocationobjecttargetWhen,targetisassignType.

5.3 Message forwarding for class methods

For the standby receiver and the complete message forwarding process, it is generally considered that only object methods can be implemented in normal developmentforward. In fact the system alsoSupport for message forwarding to class methods. Just put the smart prompt in front of the object method after it-Modified into+, can realize the class methodforward.

Example code –03 message forwarding – class method

Six, unrecognized selector

After going through thousands of mountains and rivers, still come to this step. God bypass who, then throw out our common mistakes!

-[**NSCFNumber lowercaseString]: unrecognized selector sent to instance 0x87

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’,reason:’- [**NSCFNumber lowercaseString]: unrecognized selector sent to instance 0x87

reference

link

  1. Objc source
  2. Hmmm, What’s that Selector?

The sample code

  1. 01objc_msgSend
  2. 02 Dynamic method parsing
  3. 03 Message forwarding – Standby receiver
  4. 03 Message forwarding – Instance method
  5. 03 Message forwarding – class methods