preface

In the previous article, we saw that during a slow lookup of a method, dynamic method resolution is called first if it is not found in the cache or method list. Today we will look at dynamic method resolution.

The source code

IMP lookUpImpOrForward(Class CLS, SEL SEL, ID INst, bool initialize, bool cache, bool resolver) {// IMP lookUpImpOrForward(Class CLS, SEL SEL, ID INst, bool initialize, bool cache, bool resolver) Omit some code // dynamic method parsingif(resolver && ! triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; goto retry; } // No implementation found, and method resolver didn'tImp = (imp)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); done: runtimeLock.unlock(); return imp; }Copy the code

If the class_resolveMethod method is implemented, it will be found again and continue to look at the source code

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);
        if(! lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); }}}Copy the code

If CLS is not a metaclass, use class_resolveInstanceMethod, otherwise use class_resolveClassMethod

/***********************************************************************
* _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 doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); //... Omit part of the code} / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * _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 doesn't 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

Found that the two functions are implemented similarly

  • if (! LookUpImpOrNil () is a condition that prevents programmers from writing classes that don’t inherit from NSObject, so they don’t need to start the dynamic method resolution process.
  • (Typeof (MSG))objc_msgSend, an objc_msgSend message is sent either SEL_resolveInstanceMethod or SEL_resolveClassMethod.
  • In the comments section of the code, we can see two methods: resolveInstanceMethod or resolveClassMethod, which tell us that we can implement NSObject methods ourselves.
  • Object methods use CLS, while class methods use the class_getNonMetaClass metaclass

We continue looking at lookUpImpOrNil, source code:

/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
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 the dynamic method is resolved, it is back to the method slow lookup process.

Code implementation

  • Start by defining a class
@interface LGStudent : LGPerson
- (void)sayHello;
+ (void)sayObjc;
@end

@implementation LGStudent
- (void)sayHello{
    NSLog(@"%s",__func__);
}
+ (void)sayObjc{
    NSLog(@"%s",__func__);
}
Copy the code
  • Adding dynamic 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
  • validation
        LGStudent *student = [[LGStudent alloc] init];
         [student performSelector:@selector(saySomething)];
Copy the code

The output is:

2020-01-06 10:24:42.225756+0800 LGTest[356:368647] -[LGStudent sayHello]Copy the code

We are looking at dynamic resolution of class methods

+ (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

Class methods are stored in the metaclass, so methods need to be added to the metaclass of the class when they are added

Validation:

 [LGStudent performSelector:@selector(sayLove)];
Copy the code

2020-01-06 10:31:54.084636+0800 LGTest[3757:389847] +[LGStudent sayObjc]

conclusion

  • Method dynamic resolution is divided into instance method dynamic resolution and class method dynamic resolution
  • + (BOOL)resolveInstanceMethod (SEL) SEL and add the method to the class
  • + (BOOL)resolveClassMethod:(SEL) SEL, and add the method to the metaclass
  • If dynamic method resolution is implemented, the method lookup (slow) process is revisited.