preface

Unconsciously, began to turn from the class to the method of the piece, for the method, can be said to be we usually use the most frequency, that today we will explore the nature of the method and call the process.

Nature of method

In the previous chapter, we used Clang to observe the properties of objects, but now we also use clang to observe the methods of objects.

We create a Person class, then initialize it under main.m and call the method.

Person *person = [Person alloc];
[person test];
Copy the code

Then we first convert main.m to main.cpp.

clang -rewrite-objc main.m -o main.cpp
Copy the code

Next we open main. CPP and search for int main directly.

int main(int argc, const char * argv[]) {

    /* @autoreleasepool */ 
    { __AtAutoreleasePool __autoreleasepool; 
        
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));

        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test"));
    }
    
    return 0;
}
Copy the code

Obviously, we called objc_msgSend, where ID is the message recipient and sel is the message number.

Let’s go to objc-msG-arm64.s and check it out.

/******************************************************************** * * id objc_msgSend(id self, SEL _cmd, ...) ; * IMP objc_msgLookup(id self, SEL _cmd, ...) ; * * objc_msgLookup ABI: * IMP returned in x17 * x16 reserved for our use but not used * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *Copy the code

There’s a lot of source code that’s not shown, it’s just assembly stuff.

The process is to do a quick lookup, go directly to the cache_t method to fetch buckets, and return _IMP if there is storage. If not, a normal method table lookup, also known as a slow lookup, is performed.

Outline the important code for assembly

CacheLookup NORMAL, _objc_msgSend

CacheLookup LOOKUP, _objc_msgLookup

CacheLookup NORMAL, _objc_msgSendSuper

MethodTableLookup

_lookUpImpOrForward
Copy the code

Method table lookup

Let’s first look at the source of the method table lookup.

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) { const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); // Optimistic cache lookup if (fastPath (behavior & LOOKUP_CACHE)) {imp = cache_getImp(CLS, sel); if (imp) goto done_nolock; } // lock runtimelock.lock (); // Check whether the current class is registered in the cache list checkIsKnownClass(CLS); if (slowpath(! cls->isRealized())) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); } if (slowpath((behavior & LOOKUP_INITIALIZE) && ! cls->isInitialized())) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); } runtimeLock.assertLocked(); curClass = cls; Reasonableclasscount ();) Method meth = getMethodNoSuper_nolock(curClass, sel); If (meth) {imp = meth->imp; goto done; If (slowpath((curClass = curClass->superclass) == nil)) {imp = forward_imp; break; } if (slowpath(--attempts == 0)) { _objc_fatal("Memory corruption in class list."); Imp = cache_getImp(curClass, sel); If (slowpath(imp == forward_imp)) {break; } if (fastpath(imp)) { goto done; } } if (slowpath(behavior & LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; Return resolveMethod_locked(inst, sel, CLS, behavior); } done: log_and_fill_cache(CLS, imp, sel, inst, curClass); runtimeLock.unlock(); done_nolock: if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } return imp; }Copy the code

Find the cache method.

static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
    cache_fill(cls, sel, imp, receiver);
}
Copy the code

Cahe_fill is the method cache entry we wrote last time.

And if you don’t find it, you’re gonna blink. Apparently.

Methods Flash regression analysis

 if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
 }
Copy the code

We can look at forward_IMP.

const IMP forward_imp = (IMP)_objc_msgForward_impcache;
Copy the code

So let’s just look for _objc_msgForward_impcache.

A global search brings us back to the objC-msG-arm64.s compilation.

STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.

b __objc_msgForward

END_ENTRY __objc_msgForward_impcache

ENTRY __objc_msgForward

adrp x17, __objc_forward_handler@PAGE

ldr p17, [x17, __objc_forward_handler@PAGEOFF]

TailCallFunctionPointer x17
Copy the code

Jump to __objc_msgForward and then __objc_forward_handler,

Let’s do one more global search for _objc_forward_handler

// Default forward handler halts the process.

__attribute__ ((noreturn, cold)) void

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)) ? '+' : '-', 

                object_getClassName(self**), sel_getName(sel), self);
}

void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
Copy the code

See this, unrecognized selector sent to instance, this is where we get an error if we can’t find a method.

Slow search summary

  • When the class method is called, it will be up the inheritance chain, binary search method, find will be cached, and return IMP.
  • An error is reported if it is never foundunrecognized selector sent to instance

forward

Is there any way to prevent flash back? The answer is yes, it is message forwarding.

resolveInstanceMethod

Dynamic method resolveInstanceMethod

if (slowpath(behavior & LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; Return resolveMethod_locked(inst, sel, CLS, behavior); }Copy the code

In lookUpImpOrForward, resolveMethod_locked is also called if the method cannot be found.

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); } } return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE); }Copy the code

ResolveInstanceMethod is also called. Then do a query again.

Let’s take a look at how it is implemented in nsobject.mm.

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}
Copy the code

So let’s just rewrite it in our class.

Person *p = [Person alloc];

[p go];
Copy the code

Call a go method first. Person has no implementation method in Go.

+ (BOOL)resolveInstanceMethod:(SEL)sel { NSString *methodName = NSStringFromSelector(sel); NSLog(@" method: %@",methodName); return [super resolveInstanceMethod:sel]; }Copy the code

So let’s run it and see what it prints

You can see that it does enter resolveInstanceMethod and then it flashes back.

Then we can change the code

- (void)run { NSLog(@"%s", **__FUNCTION__** ); } + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(go)) { IMP runIMP = class_getMethodImplementation(self, @selector(run)); Method runMethod = class_getInstanceMethod(self, @selector(run)); const char *runType = method_getTypeEncoding(runMethod); NSLog(@" dynamically add a method - %p",sel); return class_addMethod(self, sel, runIMP, runType); } return [super resolveInstanceMethod:sel]; }Copy the code

Output result:

[80155:4607416] Dynamically Add a method - 0x7ffF7CD8D1ba [80155:4607416] -[Person Run]Copy the code

Conclusion: in resolveInstanceMethod, we dynamically add an IMP to SEL. At this time, we can find the corresponding IMP by searching the method again.

forwardingTargetForSelector

Fast forward: forwardingTargetForSelector.

If we don’t do anything with dynamic parsing, this is where the quick flow comes in.

Let’s add the go method to person. h.

Person *p = [Person alloc];

[p go];
Copy the code

Then add the go method implementation in Person2.

At this point, we implement forwarding in Person.

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(go)) {
        return [Person2 alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

I can’t find the Person’s go method, so I’ll hand it over to Person2.

methodSignatureForSelector

Slow forward: methodSignatureForSelector.

If the first two steps are not achieved, it will come to slow forwarding.

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

Think of adding a transaction to the GO method. But exactly who will do it is uncertain.

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

For transactions, if Person2 responds to the go method, it is handed over to Person2 for processing.

Message Forwarding Flowchart

Afterword.

I feel a little confused, and it is not easy to understand what I understand, but the general process is as follows:

  1. Method cache lookup, cache methodcache_tInside, find the hash tablebuckets.
  2. Method table lookup,lookUpImpOrForward, the parent class loop through, find on the cache.
  3. Dynamic method resolution,resolveInstanceMethod.
  4. Fast forward,methodSignatureForSelectorFind other recipients.
  5. Slow forwarding,methodSignatureForSelector.