IOS & OpenGL & OpenGL ES & Metal

The message lookup phase of The Runtime has been explored. If the conditions are not met, the message forwarding phase will be entered

preface

The message forwarding of Runtime is divided into three steps:

  • Dynamic method parsing
    • Object method parsing
    • Class method resolution
  • Fast forward
  • Slowly forward
    • The method signature
    • forward

First, dynamic method analysis

Based on the previous article, we looked at lookUpImpOrForward, a method that is called resolveMethod_locked if none of this is finally met

1.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
  • judge
    • If CLS is not a metaclass, it is currently an object method and resolveInstanceMethod is called

    • If CLS is a metaclass, it is a class method. Call resolveClassMethod. If not, the resolveInstanceMethod method is repeated

  • Last calllookUpImpOrForwardMethod, returnimp

2. Object method parsingresolveInstanceMethod

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
  • Error tolerance judgment if (! LookUpImpOrNil (CLS, resolve_sel, CLS ->ISA())) looks upimpornil (CLS, resolve_sel, CLS ->ISA())). (NSObject has this method by default.)

  • If implemented, send a message to the current CLS via objc_msgSend, which calls resolveInstanceMethod: the implemented method, where we have manually added an IMP to sel

  • We then check it again with lookUpImpOrNil to get the IMP we added

  • It returns to lookUpImpOrForward, loops through it, and returns IMP

2. Class method analysisresolveClassMethod

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
  • Same fault tolerance as above

  • If implemented, send a message to the current CLS metaclass -> via objc_msgSend, call resolveClassMethod, and add imp to SEL as above

  • Note: If there is no implementation, the object method resolveInstanceMethod will be passed again because there is a special case where the parent of the metaclass is the root metaclass, and the parent of the root metaclass is NSObject, so you need to pass this method again to see if there is an implementation in NSObject.

    • In other words, all object methods and class methods that are not implemented will go inNSObjectThe inside of theresolveInstanceMethodMethods, but that does not mean that all anti-crash methods should be written here! The reason:
    • Coupling degree is too high
    • This method has been overridden in subclasses
    • Some special methods are jumped directly and the experience is poor

Code examples:

+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"%s ",__func__); NSString *name = NSStringFromSelector(sel); If ([name isEqual:@"sayHello"]) {// If ([name isEqual:@"sayHello"]) { NewIMP = class_getMethodImplementation(self, @selector(sayHappy)); Method newMethod = class_getInstanceMethod(self, @selector(sayHappy)); const char *newType = method_getTypeEncoding(newMethod); return class_addMethod(self, sel, newIMP, newType); } return [super resolveInstanceMethod:sel]; }Copy the code

Second, fast forwarding

If we continue along the above path, we will find that the method has run out and the clue will be lost…

Inside lookUpImpOrForward is the log_and_fill_cache method, which contains a logMessageSend call. One of the steps in the sequence (the methods are not shown here, but go straight to the destination) is to print the log. Where logs are stored:

snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid **()); 六四运动Copy the code

Through global exploration, it is found that the printed switch is like this code:

instrumentObjcMessageSends(BOOL flag)
Copy the code

So, let’s run the code to have a look, (just create a project, the source code can not run) preparation:

extern void instrumentObjcMessageSends(BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *objc = [[LGPerson alloc] init]; / / turn on the switch instrumentObjcMessageSends (true); [objc sayHello]; [objc sayHello]; / / off switch instrumentObjcMessageSends (false); } return 0; }Copy the code

Let’s run it. Let’s find it/tmp/msgSendsTake a look at the log files stored in this path:

As you can see from the log, the dynamic method was called after being parsedforwardingTargetForSelectorMethods. Let’s go to the official to see what this code means:

Basically, find an alternate receiver that returns an object that implements the method.

Code examples:

- (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s ",__func__); NSString *name = NSStringFromSelector(aSelector); Return [Jack alloc]; if ([name isEqual:@"sayHello"]) {return [Jack alloc]; } return [super forwardingTargetForSelector:aSelector]; }Copy the code

If there is no alternate receiver, or if the method is not implemented, slow forwarding will occur

3. Slow forwarding

If the forwardInvocation is not implemented, the forwardInvocation will be called. If the forwardInvocation is not implemented, the forwardInvocation will be called.

It’s circled in the official introduction. It needs to be rewrittenforwardInvocation:This method, I have to rewrite at the same timemethodSignatureForSelector:Methods, we continue to look at the official introduction:

Slow flow process is go first methodSignatureForSelector provides a method signature, and then walk forwardInvocation by NSInvocation message forwarding

Code examples:

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"%s ",__func__); NSString *name = NSStringFromSelector(aSelector); If ([name isEqual:@"sayHello"]) {return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; -(void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@"%s ",__func__); SEL aSelector = [anInvocation selector]; if ([[Jack alloc] respondsToSelector:aSelector]){ [anInvocation invokeWithTarget:[Jack alloc]]; }else{ [super forwardInvocation:anInvocation]; }}Copy the code

Four, the pit

In one case,resolveInstanceMethod is called twice

+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"%s ",__func__); return [super resolveInstanceMethod:sel]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s ",__func__); return [super forwardingTargetForSelector:aSelector]; } / / get the method signature - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {NSLog (@ "% s", __func__); NSString *name = NSStringFromSelector(aSelector); If ([name isEqual:@"sayHello"]) {// Method signature v- return value, @- pass object, :- pass sel return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; -(void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@"%s ",__func__); }Copy the code

Cause: In callmethodSignatureForSelectorMethod, we pass a type, and the system will do the matching, go toclass_getInstanceMethodIn this method:

Method class_getInstanceMethod(Class cls, SEL sel) { if (! cls || ! sel) return nil; // This deliberately avoids +initialize because it historically did so. // This implementation is a bit weird because it's the only place that // wants a Method instead of an IMP. #warning fixme build and search caches // Search method lists, try method resolver, etc. lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER); #warning fixme build and search caches return _class_getMethod(cls, sel); }Copy the code

It goes back to lookUpImpOrForward, which ends up at resolveMethod_locked, and then again at resolveInstanceMethod.

If we pass the resolveInstanceMethod method twice, it is because after the signature, the system does something: it matches the signature type we passed in, and calls class_getInstanceMethod again.

Five, the summary

Premise: The entire lookup process does not find the method and enters the message forwarding process

  1. Start with dynamic method parsing and add a method implementation (give SEL an IMP)

    • Object method, called+(BOOL)resolveInstanceMethod:(SEL)selmethods
    • Class method, called+(BOOL)resolveClassMethod:(SEL)selmethods
  2. Fast forward, if dynamic method parsing does not find the corresponding processing method, will come here

    • call- (id)forwardingTargetForSelector:(SEL)aSelectorMethod, return an alternate receiver
  3. Slow forwarding, if fast forwarding is not processed, it’s going to come here

    • The first step is to call-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelectorMethod gets method signature
    • The second step, call-(void)forwardInvocation:(NSInvocation *)anInvocationthroughNSInvocationTo implement message forwarding