After the learning of the previous chapter, we learned that the method is how to experience fast and slow process search, if the method search, can find the corresponding IMP, it will directly return, if not found, will enter the dynamic method analysis and message forwarding process of this chapter

Through the previous chapter, we basically understand the three stages of method search as follows:

  • Message sending phase: search method from method cache list and method list of class and parent class;

    ☞iOS Underlying Learning – Runtime methods Message history (1)

  • Dynamic parsing stage: if no method is found in the message sending stage, it will enter the dynamic parsing stage, which is responsible for dynamically adding method implementation;

  • Message forwarding stage: If the dynamic resolution method is not implemented, the message forwarding stage will be carried out, and the message will be forwarded to the receiver that can process the message for processing;

By reading the lookUpImpOrForward source code, we know that dynamic method resolution, mainly in _class_resolveMethod, and message forwarding, mainly in _objc_msgForward_impcache. From there, we’ll take a look at each one

Dynamic method parsing

In lookUpImpOrForward, a dynamic method is retried after _class_resolveMethod, and no method is found after the class, superclass, and metaclass caches and the list of methods. The method lookup process is rerun, with only one chance for dynamic method resolution

_class_resolveMethod

  • If it is not a metaclass, the instance method stored in the class is being processed
  • If it is a metaclass, it means that the class method in the metaclass is being processed, butMethods in a metaclass are instantiated in the root metaclassSo eventually the instance method of the root class will be looked up, and the instance method will be called to parse the lookup
Void _class_resolveMethod(Class CLS, SEL SEL, id inst) {// Check whether it is a metaclassif (! 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

This method is the main implementation method of instance method dynamic parsing, we through the source code line by line analysis

Static void _class_resolveInstanceMethod(Class CLS, SEL SEL, id inst) {️✅// Check whether SEL_resolveInstanceMethod is implemented. The + (BOOL) resolveInstanceMethod (SEL) SELif(! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {️✅// If NO class is found (usually a class that does not inherit child NSObject)return; } ️✅// If found, +(BOOL)resolveInstanceMethod (SEL) SEL 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 does not 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() ? '+':'-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code

Reading the source code, we know that if we want to do dynamic method parsing, we need to system method

+ (BOOL)resolveInstanceMethod:(SEL) SEL, we need to specify the IMP of an implemented method by the unimplemented method, and add to the class, implement method dynamic resolution, so we can implement a method dynamic resolution

+ (BOOL)resolveInstanceMethod:(SEL) SEL {️✅// obtain the method name to be dynamically resolvedif (sel == @selector(saySomething)) {
        NSLog(@"Speak."); ️✅// Retrieve the IMP sayHello Method and Method sayHIMP = class_getMethodImplementation(self, @selector(sayHello)); Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello)); const char *sayHType = method_getTypeEncoding(sayHMethod); ️✅// Add methods through the APIreturn class_addMethod(self, sel, sayHIMP, sayHType);
    }
    
    return [super resolveInstanceMethod:sel];
}
Copy the code

_class_resolveClassMethod

If it is a metaclass, the handling of the related class method is handled in the _class_resolveClassMethod method, which is implemented in a similar way to the instance method, except that the message is sent to fetch the metaclass

+ (BOOL)resolveClassMethod:(SEL)sel

+ (BOOL)resolveClassMethod:(SEL)sel{
    
     ifSayObjc IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass) {️✅"LGStudent"), @selector(sayObjc));
         Method sayHMethod = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc)); const char *sayHType = method_getTypeEncoding(sayHMethod); ️✅// Add the class method implementation to the metaclassreturn class_addMethod(objc_getMetaClass("LGStudent"), sel, sayHIMP, sayHType);
     }
     return [super resolveClassMethod:sel];
}
Copy the code

summary

  • After the method is searched by cache and method list, the dynamic method parsing stage is entered
  • Dynamic method analysis is divided into instance method and class method dynamic analysis
  • Instance method resolution needs to be implementedresolveInstanceMethodMethod and add the method to the class
  • Class method resolution needs to be implementedresolveClassMethodMethod and add the method to the metaclass
  • Class methods are stored in the metaclass. If you don’t implement dynamic resolution of the related class methods, because the metaclass methods are stored in the root metaclass as instance methods. Since the metaclass and the root class are created by the system and cannot be modified, you can add the corresponding instance methods to the parent NSObject of the root metaclassresolveInstanceMethodPerform dynamic parsing
  • Because the dynamic method depends on the method name and so on, it has great coupling and more judgment in unified processing, so it is seldom used in daily life

forward

In the lookUpImpOrForward method, if none of the previous steps are handled after receiving object caching, method list lookup, and dynamic method resolution, the last step in message processing, the message forwarding process, is the final step to remedy a method crash. That must be dealt with at this point.

 imp = (IMP)_objc_msgForward_impcache;
 cache_fill(cls, sel, imp, inst);
Copy the code

_objc_msgForward_impcache is also a piece of assembly code. From the code, we can know that the assembly goes through a __objc_msgForward method, and there is only a crash implementation in it, but according to the crash information, We can see that ___forwarding___ and _CF_forwarding_prep_0 are also used, but in CoreFoundation libraries, so the message forwarding is done at this point

log_and_fill_cache
/tmp/msgSends
objcMsgLogEnabled

objcMsgLogEnabled
instrumentObjcMessageSends

instrumentObjcMessageSends


extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        LGStudent *student = [LGStudent alloc] ;
        
        instrumentObjcMessageSends(true);
        [student saySomething];
        instrumentObjcMessageSends(false);

    }
    return 0;
}
Copy the code

By looking for the/TMP /msgSends file, as shown below, We found that after resolveInstanceMethod, forwardingTargetForSelector methodSignatureForSelector, doesNotRecognizeSelector, this is what we are looking for Methods.

Where resolveInstanceMethod is method dynamic resolution, doesNotRecognizeSelector is called after the last crash mentioned above

So that, in the end, the forwarding of a message is forwardingTargetForSelector and methodSignatureForSelector again, this is also a fast and slow two kinds of processes

A rapid processforwardingTargetForSelector

By looking at the – (id) forwardingTargetForSelector (SEL) aSelector method of document, we can get

  • The return object of this method is the new object that executes SEL, that is, the object that cannot handle itself will forward the message to others for the processing of the relevant method, but cannot return self, otherwise it will never be found
  • The method is more efficient if not implemented or NL will go toforwardInvocation:Method to process
  • The underlying callsobjc_msgSend(forwardingTarget, sel, ...) ;To realize the sending of messages
  • The receiver parameters and return values of the forwarded message must be the same as the original method

saySomething
LGTeacher

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

A slow processmethodSignatureForSelector

If you do not go through the fast process of message forwarding, you will enter a slow process of message forwarding to realize the slow process.

First must implement methodSignatureForSelector method, with the following documents can be obtained

  • This method generates an NSMethodSignature method signature based on the method selector SEL and returns it. This method signature encapsulates the return value type, parameter type, and other information.

Then implement methodSignatureForSelector, also must implement the – (void) forwardInvocation (anInvocation NSInvocation *); Method to process through the document we can get

  • forwardInvocationandmethodSignatureForSelectorIt has to be simultaneous, and the bottom layer will generate one by signing the methodNSInvocationPass it as an argument to the call
  • Lookup can respondInInvocationObject of the encoded message in. This object need not be the same for all messages.
  • useanInvocationThe message is sent to the object.anInvocationThe results are saved, and the run-time system extracts the results and passes them to the original sender.

We can see the NSInvocation source code

  • Encapsulates the anInvocation. Target – method caller
  • AnInvocation. Selector — Method name
  • - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;— method parameters,

In this method, we can determine to whom the message will be forwarded (target) and even modify the message parameters. Since the anInvocation stores the message selector parameters, the number of message parameters can be determined based on the method signature. So we’ll pass – (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx; Parameters can be modified. You can configure the anInvocation invoke as you wish; Can complete the message forward call

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) { // v @ :
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__);
   SEL aSelector = [anInvocation selector];

   if ([[LGTeacher alloc] respondsToSelector:aSelector])
       [anInvocation invokeWithTarget:[LGTeacher alloc]];
   else
       [super forwardInvocation:anInvocation];
}
Copy the code

summary

  • forwardingTargetForSelectorImplement a fast flow of message forwarding directly to objects that can handle related methods, and keep the methods consistent
  • methodSignatureForSelectorProvides a method signature that is used to generateNSInvocationParameter for subsequent use
  • forwardInvocationBased on theNSInvocationTo realize the final forwarding of the message
  • If the above method is not overridden, enterNSObjectIn thedoesNotRecognizeSelectorMethod, resulting in method can not find, program crash

News source

MJ God provides the relevant message forwarding C language source code

int __forwarding__(void *frameStackPointer, int isStret) { id receiver = *(id *)frameStackPointer; SEL sel = *(SEL *)(frameStackPointer + 8); const char *selName = sel_getName(sel); Class receiverClass = object_getClass(receiver); / / call forwardingTargetForSelector:if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if(forwardingTarget && forwardingTarget ! = receiver) {returnobjc_msgSend(forwardingTarget, sel, ...) ; }} / / call methodSignatureForSelector call forwardInvocation again after obtain the method signatureif (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
            NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

            [receiver forwardInvocation:invocation];

            void *returnValue = NULL;
            [invocation getReturnValue:&value];
            return returnValue; }}if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }

    // The point of no return.
    kill(getpid(), 9);
}
Copy the code

The flow chart