Note: this article aims to record the author’s learning process, only represents the author’s personal understanding, if there are inaccurate expressions, welcome to correct! Because the concept involved comes from the network, so if there is infringement, also hope to inform!

preface

This paper mainly explores the dynamic method resolution and message forwarding process in the underlying method invocation process of iOS.

The body of the

A review,

In objc_msgSend, a slow find for iOS, we explained: When a method invocation fails to find its implementation after fast or slow query steps, the program enters dynamic method resolution (resolveMethod_locked) before finally throwing an error log.

Dynamic method resolution

resolveMethod_locked

static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] resolveInstanceMethod(inst, sel, cls); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] resolveClassMethod(inst, sel, cls); if (! lookUpImpOrNil(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } } // chances are that calling the resolver have populated the cache // so attempt using it return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE); }Copy the code

resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); if (! lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, resolve_sel, 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(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', 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

ResolveClassMethod resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%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

Analyzing the source code, we can learn that:

A. In the process of method invocation, if the corresponding method is not implemented, the step of dynamic method resolution can add methods to the class to avoid errors, which is equivalent to giving a chance for fault tolerance.

B. If the instance method is not implemented, the dynamic method resolution is performed and the class method resolveInstanceMethod is invoked.

C. When a class method is called, if the corresponding method is not implemented, the dynamic method resolution is carried out. The class method resolveClassMethod is called, and the class method resolveInstanceMethod of the root class (NSObject) is also called

Debugging verification:

3. Message forwarding

From the above source code analysis, we know about the dynamic method resolution, but in the actual debugging process we looked at the stack and found that CoreFoundation itself did something between the dynamic method resolution and the final system error.

So let’s explore the CoreFoundation, and see what’s going on here? Find the CoreFoundation location.

Decompile with tools to view:

Find __forwarding_prep_0___,

Next look at _forwarding and you’ll find the following code:

Here we can see that, within CoreFoundation, a scenario in which no corresponding method is found during a method call gives the developer two opportunities for fault-tolerant processing: Fast forward forwardingTargetForSelector, slow forward (methodSignatureForSelector, forwardStackInvocation).

Functions of message forwarding:

A.f orwardingTargetForSelector steps forward can be specified by the message object, rapid relocation to the message handler, to set forward object for processing, avoid the error.

B.m ethodSignatureForSelector, forwardStackInvocation step is encapsulated into the news NSInvocation format of the drift bottle, waiting for can handle the message objects to deal with, also can avoid the error.