A developer can send a message to any object (including nil) while coding, and compilation will pass even if the message is not implemented. At compile time, it is only determined that the message will be sent to the receiver, and how the receiver will respond and process the message is responsible at Runtime for the creation of the object that sends and forwards the message.

  • Objc_msgSend introduction
  • Messaging Phase
  • Message Forwarding
  • Flow chart of sending and forwarding messages
Objc_msgSend introduction

The Object message in object-c is converted to the objc_msgSend method at compile time:

/* self: the method accepts the object op: method selector */
id objc_msgSend(id self, SEL op, ...)
Copy the code
// SEL type definition typedef struct objc_selector *SEL;Copy the code

Objc_selector is a C string mapped to a method. Methods of different classes in Objc with the same method name have the same method selector. Methods with the same name and different parameter types have the same method selection.

The objc_msgSend method does several things:

  1. Check if selectors need to be ignored;

  2. Check if target is nil

    • If it is nil, and there is a corresponding function that handles nil, jump to that function;
    • If it is nil, there is no corresponding handler, the scene is cleaned up and returned; This is also why methods called by nil objects don’t crash.
  3. If target is not nil, the corresponding class is found through the isa pointer to the object, and the IMP implementation of the method is found in the cache in the class object.

    If it does, it jumps in, if it doesn’t, it looks in the class’s method list, if it doesn’t, it looks in the parent class’s cache and method list, all the way to the root class NSObject. If not, the message is forwarded.

Messaging Phase

There is no secret in the source code. Here is a look at the implementation of method lookup and forward from runtime source code. Key entry methods:

// objc-runtime-new.mm
/* CLS: class name sel: method selector inst: Initialize = NO; +initialize = YES; If cache is NO, the lock search step is skipped and the lock search step is performed directly. The lock is used to prevent dynamic methods from being added during cache read, ensuring thread safety. Resolver: Whether to parse messages if imPs cannot be found in inherited classes. * /
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
Copy the code
  1. Look in the cache of this class

    // Try this class's cache.
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    Copy the code
  2. Look in the list of methods for the class

    // Try this class's method lists.
        meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    Copy the code
  3. Look in the parent class (cache & method list)

    // Try superclass caches and method lists.
        curClass = cls;
        // Iterate up the inheritance relationship until nsobject. superclass = nil
        while ((curClass = curClass->superclass)) {
            // Superclass cache.
            // Look it up in the parent's cache
            imp = cache_getImp(curClass, sel);
            if (imp) {
              // Set sel IMP to _objc_msgForward_impcache (step 5) Imp == (imp)_objc_msgForward_impcache == (imp)_objc_msgForward_impcache == (imp)_objc_msgForward_impcache Terminates the circular lookup in the parent class.
                if(imp ! = (IMP)_objc_msgForward_impcache) {// Found the method in a superclass. Cache it in this class.
                    // Find the IMP in the parent class cache, save the IMP in the parent class cache. What is in the cache of this class? Because the first step of the search is started by the cache of the class, improve the search efficiency.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break; }}// Superclass method list.
            // The list of superclass methods is not looked up in the cache
            meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                // Put it in the cache of this class
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                gotodone; }}Copy the code
  4. None found, try method parsing once.

    // No implementation found. Try method resolver once.
        // 4 Failed to find the attempt to parse once
        if(resolver && ! triedResolver) { runtimeLock.unlockRead();/* Method parsing */
            _class_resolveMethod(cls, sel, inst);
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            triedResolver = YES;
            goto retry;
        }
    Copy the code

    The _class_resolveMethod method is implemented as follows:

    void _class_resolveMethod(Class cls, SEL sel, id inst)
    {
      ResolveInstanceMethod :(SEL) SEL + (BOOL)resolveClassMethod:(SEL) SEL If the developer dynamically adds IMP to the resolve method, it will be found and cached in the retry method lookup process */
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            _class_resolveClassMethod(cls, sel, inst);
            if(! lookUpImpOrNil(cls, sel, inst,NO/*initialize*/.YES/*cache*/.NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); }}}Copy the code

    _class_resolveInstanceMethod

    static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
    {
        if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                             NO/*initialize*/.YES/*cache*/.NO/*resolver*/)) 
        {
            // Resolver not implemented.
            return;
        }
    
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
    
        // Cache the result (good or bad) so the resolver doesn't fire next time.
        // +resolveInstanceMethod adds to self a.k.a. cls
        IMP imp = lookUpImpOrNil(cls, sel, inst, 
                                 NO/*initialize*/.YES/*cache*/.NO/*resolver*/);
    
        if (resolved  &&  PrintResolving) {
            if (imp) {
                _objc_inform("RESOLVE: method %c[%s %s] "
                             "dynamically resolved to %p", 
                             cls->isMetaClass() ? '+' : The '-', 
                             cls->nameForLogging(), sel_getName(sel), imp);
            }
            else {
                // Method resolver didn't add anything?
                _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                             ", but no new implementation of %c[%s %s] was found",
                             cls->nameForLogging(), sel_getName(sel), 
                             cls->isMetaClass() ? '+' : The '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code

    Note:

    • LookUpImpOrNil is implemented much like lookUpImpOrForward, except that when IMP is not found, it is assigned to **_objc_msgForward_impcache**, stored in the cache, and returned. For subsequent message forwarding, as shown in Step 5. LookUpImpOrNil returns nil and does not forward the message.
    • _objc_msgForward_impcache is a flag placed in the IMP cache. The flag does not find IMP in the inherit system or in the resolve method, which will be used for message forwarding. If the IMP corresponding to SEL is found to be _objc_msgForward_impcache, the search for the parent class is stopped.

    ResolveInstanceMethod resolveInstanceMethod

    #import "Person.h"
    The Person class declares the - (void)sweepFloor method, but does not implement it. * /
    // Custom method implementation
    void sweepImp (id self,SEL _cmd){
        NSLog(@"sweepImp");
    }
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if ([NSStringFromSelector(sel) isEqualToString:@"sweepFloor"]) {
          // Add method - (void) sweepImp for sweepFloor
            class_addMethod(self, sel, (IMP)sweepImp, "v@:");
        }
        return [super resolveInstanceMethod:sel];
    }
    To help the run-time system, the compiler encodes the return and incoming parameters of each method as a string and associates that string with the method selector. class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, Const char * _Nullable types) The last parameter types of class_addMethod is the method return parameter & the encoded character of the passed parameter type, which needs to be passed in manually by the developer. - (void)getTypes { char *voidChar = @encode(void); char *seletorChar = @encode(SEL); char *idChar = @encode(id); NSLog (@ parameter coding: % s % s "% s", voidChar, seletorChar, idChar); // Parameter encoding: v @ :} */
    Copy the code
  5. If there is no IMP in the resolve method for Message Forwarding

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    
    // 5 If the resolve method does not have IMP, assign IMP to _objc_msgForward_impcache, store IMP in cache, and then send Message Forwarding. IMP for external use must be converted to _objc_msgForward or _objc_msgForward_stret
    //_objc_msgForward_impcache is a function pointer stored in the method cache.
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    Copy the code

    At this point, the message sending phase is complete and the message forwarding phase is entered.

Message Forwarding

After _objc_msgForward is executed, the objc_forward_handler function is called. The value of objc_forward_handler is objc_defaultForwardHandler:

objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self))?'+' : The '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

Copy the code

This print is an implementation of the crash log we often see in Xcode crashes.

Before executing objc_forward_handler, developers can implement message forwarding by overriding the following methods of the class.

  • Specifies a new recipient for the method

    #import "Person.h"
    #import "SweepRobot.h"
    // Class Person, declared sweepFloor method but not implemented, forwarded to sweepFloor implementation SweepRobot
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSLog(@"%s_%@",__FUNCTION__,NSStringFromSelector(aSelector));
        if ([NSStringFromSelector(aSelector) isEqualToString:@"sweepFloor"]) {
            return [SweepRobot new];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    Copy the code
    #import "SweepRobot.h"
    
    @implementation SweepRobot
    - (void)sweepFloor {
        NSLog(@"%@ %s implementation"[self class],__func__);
    }
    @end
    Copy the code
  • Get the method signature, create THE NSInvocation

    @interface Person(a)
    @property (nonatomic.strong)SweepRobot *robot;
    @end
      
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSLog(@"%s",__FUNCTION__);
        return [self.robot methodSignatureForSelector:aSelector];
    }
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"%s anInvocation:%@",__FUNCTION__,anInvocation);
        [anInvocation invokeWithTarget:self.robot];
    }
    
    - (SweepRobot *)robot {
        if(! _robot) { _robot = [SweepRobot new]; }return _robot;
    }
    Copy the code
Flow chart of sending and forwarding messages