preface

In the slow lookup of objc_msgSend, when imp is not found (not only for the current class, but also for its parent class up to NSObject), behavior is determined and resolveMethod_locked is returned directly.

The idea is that Apple gives you a chance to live with it. If the resolveMethod_locked method does not handle it, then a crash exception will be thrown. We’ll focus on the principle of resolveMethod_locked, or dynamic method resolution.

Dynamic method resolution

// If imp is not found, perform a method resolution
// The reason for executing once:
If, behavior = behavior ^ LOOKUP_RESOLVER = 3 ^ 2 = 1. If, behavior = behavior ^ LOOKUP_RESOLVER = 3 ^ 2 = 1
Behavior = 1, LOOKUP_RESOLVER = 2, 1&2 = 0
if (slowpath(behavior & LOOKUP_RESOLVER)) {
    behavior ^= LOOKUP_RESOLVER;
    // Dynamic method resolution
    return resolveMethod_locked(inst, sel, cls, behavior);
}
Copy the code
  • This method only executesAt a time, similar to simple interest.
  • So we do the math, and we end up withbehavior = 1.

ResolveMethod_locked Source analysis

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked(a);ASSERT(cls->isRealized());
    runtimeLock.unlock(a);// Not a metaclass
    if (! cls->isMetaClass()) {
        // Object method dynamic resolution
        resolveInstanceMethod(inst, sel, cls);
    } 
    / / the metaclass
    else {
        // Dynamic resolution of class methods
        resolveClassMethod(inst, sel, cls);
        Behavior = 1
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            // If imp is not found
            // Object method dynamic resolution
            resolveInstanceMethod(inst, sel, cls); }}// Look up the cache again and use it. Behavior = 1
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
Copy the code
  • If not metaclass, execute object method dynamic protocolresolveInstanceMethod
  • If metaclass, first implement the class method dynamic protocolresolveClassMethodAnd thenlookUpImpOrNilTryCacheTry to findimpIf not foundimp, implements the metaclass object method dynamic protocolresolveInstanceMethod.
  • The last againlookUpImpOrForwardTryCacheLook for the cache.

LookUpImpOrNilTryCache and lookUpImpOrForwardTryCache

When the metaclass, will try again to find lookUpImpOrNilTryCache, then finally performs lookUpImpOrForwardTryCache, so what is the difference between the two?

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    Behavior = 5
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
    Behavior = 1
    return _lookUpImpTryCache(inst, sel, cls, behavior);
}
Copy the code
  • Call a method together_lookUpImpTryCache
  • cls,behaviorParameter, which also leads to two essential differences, whether the message is forwarded or not.lookUpImpOrForwardTryCacheMessage forwarding, andlookUpImpOrNilTryCacheMessage forwarding is not performed.

_lookUpImpTryCache

Since lookUpImpOrNilTryCache and lookUpImpOrForwardTryCache will perform _lookUpImpTryCache, specific analysis of the source code below:

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked(a);if (slowpath(! cls->isInitialized())) {
        // If the class is not initialized, the slow lookup process is performed because the class was initialized during the slow lookup process
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    // If the class is already initialized, look in the cache again
    IMP imp = cache_getImp(cls, sel);
    // If it exists
    if(imp ! =NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    // Share the cache lookup dynamically
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    if (slowpath(imp == NULL)) {
        // If not, execute the slow lookup process again
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    
done:
    // For slow lookup dynamic method resolutions, since behavior = 5, the if will not be executed
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    / / returns the imp
    return imp;
}
Copy the code
  • isInitialized()Almost never, because the class is already initialized in the slow lookup process.
  • cache_getImpLook for the cache. If you can find it, returnimp; If not, look for the shared cache, and if not, execute againlookUpImpOrForwardSlow search process.
  • When the slow lookup process is executed again, it does not executeresolveMethod_lockedThe dynamic method is resolved.

ResolveInstanceMethod and resolveClassMethod is in before returning to lookUpImpOrForwardTryCache call. So what exactly do they do? How should it be used?

ResolveInstanceMethod Object method dynamic resolution

When an object is called to the objc_msgSend method, and it is not found by fast or slow lookup, the resolveInstanceMethod is executed.

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked(a);ASSERT(cls->isRealized());
    // Get the class method +resolveInstanceMethod
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    // Do a metaclass lookup to see if the 'resolveInstanceMethod' method, which is the class method, is implemented. Return directly if not implemented.
    // If the metaclass of the current class is NSObject, it will not return, because NSObject implements it by default.
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        return;
    }
    // The system sends a resolveInstanceMethod method. Since the receiver of the message is CLS, resolveInstanceMethod is a class method.
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);
    / / find the imp
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
    // Logs are printed
    if(resolved && PrintResolving) { ... }}Copy the code
  • To obtainresolveInstanceMethodThe reason why class methods areClass methodBecause the system automatically calls it in the following processobjc_msgSend, whose message receiver iscls, so it isClass method.
  • To the metaclassresolveInstanceMethodIs not empty here.
  • The system sends it to the class+resolveInstanceMethodThe message.
  • To find theimp. There’s no direct return at this pointimp“, but only to judge.
  • resolved  &&  PrintResolvingYes Log printing

ResolveInstanceMethod Process analysis

  • In the implementationresolveInstanceMethodMethod, and executeslookUpImpOrNilTryCacheDo a lookup to find if the metaclass is implementedresolveInstanceMethodMethods. Among themcls->ISA()It’s a metaclass, and we’ll find itNSObjectIn the. If it is not implemented, thenresolveInstanceMethodCached in the metaclass. (this processNSObjectImplemented by default)
  • And then the system sendsresolveInstanceMethodThe message.
  • Performed againlookUpImpOrNilTryCacheZLSubObjectLook forimp, whose purpose is toimpjoinZLSubObjectIf you can’t find it, it will be stored_objc_msgForward_impcache.
  • Return to continue executionlookUpImpOrForwardTryCacheIn this case, the message slow lookup will not be performed. Because the corresponding cache has already been written. This time it’s going to get it from the cacheimp_objc_msgForward_impcache. The message is forwarded directly.

Conclusion:

  • lookUpImpOrNilTryCache You just insert the method into the cache
  • lookUpImpOrForwardTryCache Get from the cacheimp
  • That’s why it’s implemented, rightlookUpImpOrNilTryCacheTo find theimp“, but did not return, but carried out againlookUpImpOrForwardTryCacheTo look upimp, the reason why it is invoked twice.

ResolveInstanceMethod case study

Continue with the example from the previous objc_msgSend Slow Lookup – case, where ZLObject is improved as follows:

@interface ZLObject : NSObject

+ (void)classMethod1;
+ (void)classMethod2;

- (void)instanceMethod1;
- (void)instanceMethod2;

@end

@implementation ZLObject

+ (void)classMethod1 {
    NSLog(@"%s",__func__);
}

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

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"resolveInstanceMethod: %@-%@".self.NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

@end
Copy the code

The execution code is as follows:

ZLSubObject *subObj = [[ZLSubObject alloc] init];
[subObj subInstanceMethod];
[subObj instanceMethod1];
[subObj instanceMethod2];
Copy the code

The print result is as follows:

-[ZLSubObject subInstanceMethod]
-[ZLObject instanceMethod1]
resolveInstanceMethod: ZLSubObject-instanceMethod2
resolveInstanceMethod: ZLSubObject-instanceMethod2
-[ZLSubObject instanceMethod2]: unrecognized selector sent to instance 0x10060e470
Copy the code
  • It still crashes, but there are two more prints:resolveInstanceMethod: ZLSubObject-instanceMethod2.
  • That means it’s implemented in the class+ (BOOL)resolveInstanceMethodIt’s available.

But why print two: resolveInstanceMethod: ZLSubObject-instanceMethod2?

Add a breakpoint to + (BOOL)resolveInstanceMethod and execute:

The first entry to the stack is as follows:

The second entry to the stack is as follows:

In combination withresolveInstanceMethodThe analysis is as follows:

  • First executionresolveInstanceMethodMethod will eventually be executedlookUpImpOrForwardTryCacheMessage forwarding. When the message is forwarded, it executesclass_getInstanceMethodMethods.
  • class_getInstanceMethodThe source code is as follows:
Method class_getInstanceMethod(Class cls, SEL sel)
{
   if(! cls || ! sel)return nil;
    Behavior = LOOKUP_RESOLVER
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    // Look for CLS method
    return _class_getMethod(cls, sel);
}
Copy the code
  • It was executed againlookUpImpOrForwardAnd,behavior = LOOKUP_RESOLVER, so message forwarding is not carried out this time, which will not cause an infinite loop. That’s why it’s printed the second time.

ResolveInstanceMethod case improvement

Based on the analysis above, if the IMP is implemented, the message will not be forwarded, and the second print will not be done. The related code is modified as follows:

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

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"resolveInstanceMethod: %@-%@", self, NSStringFromSelector(sel));
    if (sel == @selector(instanceMethod2)) {
        IMP imp = class_getMethodImplementation(self, @selector(instanceMethod1));
        Method m = class_getInstanceMethod(self, @selector(instanceMethod1));
        const char * type = method_getTypeEncoding(m);
        return class_addMethod(self, sel, imp, type);
    }
    return [super resolveInstanceMethod:sel];
}
Copy the code

The print result is as follows:

resolveInstanceMethod: ZLSubObject-instanceMethod2
-[ZLObject instanceMethod1]
Copy the code

When instanceMethod2 is obtained, it is dynamically added to execute other methods that have been implemented, such as instanceMethod1.

ResolveClassMethod Dynamic resolution of class methods

There are many similarities between class method dynamic resolution and object method dynamic protocol. Class method dynamic resolution source code as follows:

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked(a);ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    // Check whether the current class implements' resolveClassMethod ', and return if it does not.
    // This will not return, because NSObject implements it by default.
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        return;
    }
    / / the metaclass
    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        // Get non-metaclasses
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        if(! nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized", nonmeta->nameForLogging(), nonmeta); }}// The system sends a non-metaclass resolveClassMethod class method
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
    / / find the imp
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
    // Logs are printed
    if(resolved && PrintResolving) { ... }}Copy the code

Process analysis:

  • In the implementationresolveClassMethodMethod, and executeslookUpImpOrNilTryCacheDo a lookup to find if the class is implementedresolveClassMethodMethods. If it is not implemented, thenresolveClassMethodCached in the class. (this processNSObjectImplemented by default)
  • Then get the non-metaclassnonmetaThe purpose is to prevent non-metaclasses from not being implemented. Where get non-metaclass methodsgetMaybeUnrealizedNonMetaClass, the main logic is: if not metaclass direct returnA metaclass; If it isClass of ISAPoint to theTheir own, then returnNSObject; If it is any other class, loop to get the parent class until it is foundNSObjectSo far; And other classes of judgment logic, here will not be repeated.
  • The system sendsresolveClassMethodThe message.
  • Performed againlookUpImpOrNilTryCacheZLSubObjectLook forimp, whose purpose is toimpjoinZLSubObjectIf you can’t find it, it will be stored_objc_msgForward_impcache.
  • Return to continue executionlookUpImpOrForwardTryCacheIn this case, the message slow lookup will not be performed. Because the corresponding cache has already been written. This time it’s going to get it from the cacheimp_objc_msgForward_impcache. The message is forwarded directly.
  • resolved  &&  PrintResolvingYes Logs are printed.

Conclusion:

  • lookUpImpOrNilTryCache You just insert the method into the cache
  • lookUpImpOrForwardTryCache Get from the cacheimp

ResolveClassMethod case analysis

Add the following code to ZLObject:

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"resolveClassMethod: %@-%@".self.NSStringFromSelector(sel));
    return  [super resolveClassMethod:sel];
}
Copy the code

The execution code is as follows:

[ZLSubObject classMethod2];
Copy the code

The print result is as follows:

resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-classMethod2
resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-classMethod2
+[ZLSubObject classMethod2]: unrecognized selector sent to class 0x100008310
Copy the code
  • It still crashes, but you will find multiple prints:ZLSubObject-. Among themclassMethod2It’s printed twice. (encodeWithOSLogCoderNot to explore for the moment)
  • When callinglookUpImpOrNilTryCacheCould not findimpIs calledresolveInstanceMethodTo find (at this pointclsIs a metaclass, because class methods are essentially metaclass object methodslookUpImpOrForwardTryCache, that is, message forwarding.
  • Finally, the method dynamic resolution is executed again when the message is forwarded.
  • At this point, print two reasons andresolveInstanceMethodSimilarly, the second time, the metaclass was executed for message forwardingclass_getInstanceMethod.

ResolveClassMethod case improvement

The related code is modified as follows:

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"resolveClassMethod: %@-%@".self.NSStringFromSelector(sel));
    if (sel == @selector(classMethod2)) {
        Class metaClass = objc_getMetaClass("ZLObject");
        IMP imp = class_getMethodImplementation(metaClass, @selector(classMethod1));
        Method m = class_getClassMethod(self.@selector(classMethod1));
        const char * type = method_getTypeEncoding(m);
        return class_addMethod(metaClass, sel, imp, type);
    }
    return  [super resolveClassMethod:sel];
}
Copy the code

The print result is as follows:

resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-encodeWithOSLogCoder:options:maxLength:
resolveClassMethod: ZLSubObject-classMethod2
+[ZLObject classMethod1]
Copy the code

When you get classMethod2, you dynamically add it to execute other methods that are already implemented, such as classMethod1.

Dynamic method resolution – classification

Because class method dynamic resolution is executed when class method is called, resolveInstanceMethod of metaclass is executed when lookUpImpOrNilTryCache is not found.

// Dynamic resolution of class methods
resolveClassMethod(inst, sel, cls);
Behavior = 1
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
    // If imp is not found
    // Object method dynamic resolution
    resolveInstanceMethod(inst, sel, cls);
}
Copy the code

Therefore, both object method dynamic resolution and class method dynamic resolution can be handled in the metaclass resolveInstanceMethod.

According to isa’s bitmap, NSObject is both the root class and the metaclass, calling the + method on NSObject into the metaclass of NSObject, which is NSObject itself, This is done by using NSObject’s resolveInstanceMethod method.

Add a class of NSObject by:

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"resolveInstanceMethod: %@-%@".self.NSStringFromSelector(sel));
    if (sel == @selector(instanceMethod2)) {
        IMP imp = class_getMethodImplementation(self.@selector(instanceMethod1));
        Method m = class_getInstanceMethod(self.@selector(instanceMethod1));
        const char * type = method_getTypeEncoding(m);
        return class_addMethod(self, sel, imp, type);
    } else if (sel == @selector(classMethod2)) {
        Class metaClass = objc_getMetaClass("ZLObject");
        IMP imp = class_getMethodImplementation(metaClass, @selector(classMethod1));
        Method m = class_getClassMethod(self.@selector(classMethod1));
        const char * type = method_getTypeEncoding(m);
        return class_addMethod(metaClass, sel, imp, type);
    }
    return NO;
}
Copy the code

Call object methods and class methods respectively:

ZLSubObject *subObj = [[ZLSubObject alloc] init];
[subObj instanceMethod2];

[ZLSubObject classMethod2];
Copy the code

Execution result:

resolveInstanceMethod: ZLSubObject-instanceMethod2
-[ZLObject instanceMethod1]
resolveInstanceMethod: ZLSubObject-classMethod2
+[ZLObject classMethod1]
Copy the code

This implements resolveInstanceMethod in the class of NSObject, dealing with both class methods and instance methods. The two call parameters are different, one is a class call, one is a metaclass call.

conclusion

  • lookUpImpOrNilTryCacheCache only methods,lookUpImpOrForwardTryCache, from the cacheimpTo forward messages.
  • resolveInstanceMethodresolveClassMethodThe reason for printing two is that the second time the message forwarding is performed, the metaclass is executedclass_getInstanceMethod.
  • When executing a class method dynamic resolution, the metaclass is called if there is a missresolveInstanceMethodObject methods. This also conforms to the principle that calling a class method is actually calling an object method of the metaclass.
  • inNSObjectIn the classification ofresolveInstanceMethod, handles both class and instance methods.
  • The return value of the dynamic method resolution does not affect functionality, only log printing.

Dynamic method resolution flow chart

aop & oop

The significance of dynamic method resolution is that it gives Apple a chance to resolve the error when it looks for imp and can’t find it. For example, you can implement + resolveInstanceMethod in the class of NSObject, so that any imp that is not found will be listened to in the resolveInstanceMethod.

So in the actual development process, can you handle this way? For example, in my own project, the class name can be distinguished by prefix, module, transaction, type, etc. (for example: ZLLoginCodeController), when the method is found to have problems, can be fault-tolerant processing. For example, when getLoginCodeEvent occurs when obtaining the login verification code, report the problem. In fact, there are certain disadvantages.

This approach is called aspect programming.

oop

Oop is a commonly used Programming method. It is characterized by encapsulation, inheritance, and polymorphism. Oop achieves the three main goals of software engineering: reusability, flexibility, and extensibility. OOP = Object + class + inheritance + polymorphism + message, where the core concepts are classes and objects.

It is common to extract common classes, but there are disadvantages to following them, which are strong dependencies and strong coupling. However, developers are more concerned with the business, so when dealing with public classes, try not to intrude as much as possible. Injecting code dynamically has no effect on the original method. The methods and classes to cut into are the cut points.

aop

Aop is Aspect Oriented Programming, and AOP is an extension of OOP. Is a derivative paradigm of functional programming. Using AOP can isolate each part of the business logic, so that the coupling degree between each part of the business logic is reduced, the reusability of the program is improved, and the efficiency of development is improved.

But there are drawbacks:

  • Code redundancy: as in the above exampleif-elseJudge, if the method is more, still have to writeif-elseJudgment.
  • Wasted performance: Methods are called multiple times, wasting performance. If the hit is ok, if the miss is going to go multiple times, there will bePerformance overhead.
  • Repeat processing: if other modules have done the corresponding processing, repeat this section, may not be executed.

Dynamic method resolution is the previous phase of the message forwarding mechanism. Meaning that if you do fault tolerance here, the rest of the process is cut off. Then there is no point in forwarding the process. So it makes more sense to do AOP in the latter part of the process.