preface

  • IOS low-level exploration — Alloc, init, new exploration
  • IOS Low-level Exploration – Memory byte alignment analysis
  • IOS Low-level Exploration – The nature of objects
  • IOS Low-level Exploration – Isa initialization & Pointing Analysis
  • IOS Low-level exploration – Class structure analysis
  • IOS Low-level Exploration — Cache_T Analysis
  • IOS Low-level Exploration – method lookup flow

Method lookup failure

We’ve seen how methods look up in our iOS Low-level exploration article, method Look Up. If the method isn’t found, lookUpImpOrForward() has a section reserved for failure.

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { ... Omit some code... // No implementation found. Try method resolver once.if(resolver && ! triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); triedResolver = YES; goto retry; } // No implementation found, and method resolver did not help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache;  cache_fill(cls, sel, imp, inst);done:
    runtimeLock.unlock();

    return imp;
}

Copy the code
  • // No implementation found. Try method resolver once.This passage tells us,impIf not, it’s still method parsing.
  • // No implementation found, and method resolver did not help. // Use forwarding.If method analysis doesn’t help us, use itforwardingForward.

Let’s start analyzing the following two processes.

Dynamic method parsing

1. Function entry

 if(resolver && ! triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); triedResolver = YES; goto retry; }Copy the code

_class_resolveMethod is the function we need to investigate.

2. Analytical methods

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    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); // It has been processedif(! lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/) {// object method resolver _class_resolveInstanceMethod(CLS, sel, inst); }}}Copy the code

Distinguish between metaclass and class

  • Object method parsing_class_resolveInstanceMethod.
  • Class method resolution_class_resolveClassMethod. And after class method analysis, still not foundimp, will still go object method parsing.

3. Object and class method parsing

3.1 Object method parsing

/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
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 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*/); . Omit some code... }Copy the code

3.2 Class method analysis

/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(_class_getNonMetaClass(cls, inst), SEL_resolveClassMethod, sel); // Cache the result (good or bad) so the resolver does not fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); . Omit some code... }Copy the code

The two functions are actually similar.

  • if (! lookUpImpOrNil()This condition mainly prevents programmers from writing classes that do not inherit fromNSObject, there is no need to start parsing the process dynamically.
  • (typeof(msg))objc_msgSend, aobjc_msgSendMessage is sentSEL_resolveInstanceMethodorSEL_resolveClassMethod.
  • We can see this in the comment section of the coderesolveInstanceMethodorresolveClassMethodThese two methods tell us that we can implement the parent class ourselvesNSObjectMethods.

3.2 lookUpImpOrNil

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}
Copy the code

After dynamic method parsing, we returned to the slow method lookup phase of the iOS Low-level Exploration – Method Lookup Flow article.

4. Method analysis examples

4.1 Example of object method parsing

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    if (sel == @selector(saySomething)) {
        NSLog(@"Speak.");
        
        IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
    
        Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
        
        const char *sayHType = method_getTypeEncoding(sayHMethod);
        
        return class_addMethod(self, sel, sayHIMP, sayHType);
    }
    
    return [super resolveInstanceMethod:sel];
}
Copy the code

Replace the unimplemented method saySomething with another implemented method sayHello.

4.2 Class method parsing examples

+ (BOOL)resolveClassMethod:(SEL)sel{
    
    if (sel == @selector(sayLove)) {
        
        IMP imp = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(sayObjc));
        
         Method method = class_getClassMethod(objc_getMetaClass("LGStudent"), @selector(sayObjc));
        
        const char *types = method_getTypeEncoding(method);
        
        return class_addMethod(objc_getMetaClass("LGStudent"), sel, imp, types);
    }
    return [super resolveClassMethod:sel];
}
Copy the code

The important thing to note here is that since class methods are stored in the metaclass, we need to add methods to the metaclass for dynamic method resolution to succeed.

4.3 Leaving questions

  1. The dynamic parsing method did not go to the processing, found this problem, will come in twice?

This question will be explained at the very end.

  1. In analytic method analysis, in dynamic method analysis of class methods, but also the following object method parsing purpose?
_class_resolveClassMethod(cls, sel, inst); // It has been processedif(! lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/) {// object method resolver _class_resolveInstanceMethod(CLS, sel, inst); }Copy the code
  • We’re throughIOS Low-level Exploration – Isa initialization & Pointing AnalysisIn the analysis of this article, both classes (for object methods) and metaclasses (for class methods) will eventually point to their parent classesNSObject.
  • In the process of finding a method, we explore it, actually byselthenameTo match, regardless of the underlying – and + methods.
  • The goal is to get us inNSObjectDo the same thing in.

Methods the forwarding

If dynamic method resolution is not processed, message forwarding comes next.

1. Analysis of forwarding sources

1.1 Log Printing Conditions

In the iOS Low-level Exploration article, The Method Lookup Process, we learned that once a method is found, it enters the caching process.

static void
log_and_fill_cache(Class cls, Class implementer, Method meth, SEL sel)
{
#if SUPPORT_MESSAGE_LOGGING
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if(! cacheIt)return;
    }
#endif
    _cache_fill (cls, meth, sel);
}
Copy the code
  • if (objcMsgLogEnabled)If this condition is true, the log is printed.

1.2 Log Printing Entrance

Do a full search of objcMsgLogEnabled and you can find

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if(objcMsgLogFD ! = -1) fsync (objcMsgLogFD); objcMsgLogEnabled =enable;
}
Copy the code
  • A function of conditional assignment.
  • View function definitionsOBJC_EXPORT void instrumentObjcMessageSends(BOOL flag)Is an external use.

1.3 Log Location

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char	buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true;
        }
    }

    // Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : The '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}
Copy the code
 instrumentObjcMessageSends(true);
 [student saySomething];
 instrumentObjcMessageSends(false);
Copy the code
  • /tmp/msgSendsThis is where the logs are stored.
  • instrumentObjcMessageSendsMethods need to be definedexternRepresents external use.
  • commond + shift + GTo view/tmp/msgSends

1.4 Viewing Print Logs

+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject methodSignatureForSelector:
- LGStudent NSObject class
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject doesNotRecognizeSelector:
- LGStudent NSObject class

+ __NSCFString NSObject resolveInstanceMethod:
+ __NSCFString NSObject resolveInstanceMethod:
Copy the code

The reason each method prints twice is because it has one and one done by super.

  • Through log analysis we can see that the flow isresolveInstanceMethodforwardingTargetForSelectormethodSignatureForSelectordoesNotRecognizeSelectorAn error is reported if each step is not implemented.

Note here, can solve the problem left by ‘4.3’, why will come in twice. Look at the log file once again and the __NSCFString system level is processed once.

2. Fast forwarding

Through the log above we can see forwardingTargetForSelector fast forwarding process.

2.1 NSObject.mmFile view Definition

+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}
Copy the code

You can let subclasses do that themselves.

2.2 Apple’s official definition

This means that an unrecognized message can be processed by another object.

2.3 Specific Implementation

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGStudent *student = [LGStudent alloc] ;
        [student saySomething];
    }
    return 0;
}

@interface LGTeacher : NSObject

@end

@implementation LGTeacher

- (void)saySomething{
    NSLog(@"%s",__func__);
}

@end

@implementation LGStudent

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(saySomething)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}


@end
Copy the code

LGStudent does not implement the saySomething method, but LGTeacher does, and passes it directly to LGTeacher’s object.

3. Slow forwarding

If the fast forwarding stage is not realized, it will enter the slow forwarding stage

3.1 Method Signature

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(saySomething)) { 
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
Copy the code

So that’s where you sign the method and throw it out, whoever wants to handle it.

3.2 Forwarding Callback

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    
    SEL aSelector = [anInvocation selector];
    
    if ([[LGTeacher alloc] respondsToSelector:aSelector])
        [anInvocation invokeWithTarget:[LGTeacher alloc]];
    else
        [super forwardInvocation:anInvocation];
}
Copy the code

If it can be handled by someone else, throw it to another handler, otherwise the system won’t handle the method.

conclusion

1. Process summary

This is the end of the analysis of message forwarding, and the following is a summary.

  • afterobjc_msgSendMethod fast search and slow search results can not enter the message dynamic parsing.
  • Dynamic parsing process_class_resolveMethod()If there is processing, it will follow the processing. If there is no processing, it will enter the stage of rapid message forwarding.
  • Fast message forwarding phaseforwardingTargedForSelector()If there is processing, it will be implemented according to the object handed to be processed. If there is processing, it will enter the slow forwarding stage.
  • Slow forwarding of messagesmethodSignatureForSelector(), sign the method, throw the method out,forwardInvocation()To process the message.
  • If the above intermediate steps are not performed, it is entereddoesNotRecognizeSelectorAn error.

The attached flow chart is as follows:

2. Observe the printing on the forwarding process console

Console print order is according to resolveInstanceMethod – > forwardingTargetForSelector – > methodSignatureForSelector – > resolveInstanceMethod – > forwardInvocation.

  • The penultimate step calls the dynamic method resolution again, and this is actually a method signature returned from the previous step, the next step will look for that signature, the search process will go through again, this is the signature matching process, call again_class_getInstanceMethodIt’s done at the system level.

The road of learning, forge ahead

The shortcomings can be pointed out in the comments section