When we look for a method that is not in the cache, and the parent class is not, then we end up giving IMP = forwarding. const IMP forward_imp = (IMP)_objc_msgForward_impcache; What is this and what is the goal of this exploration

Error method handling – message handling

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{...constIMP forward_imp = (IMP)_objc_msgForward_impcache; .if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    // No implementation found, and method resolver didn't help.
    // Use forwarding.
    imp = forward_imp;
    break; }}Copy the code
  • _objc_msgForward_impcache location in source code found called__objc_msgForward

  • __objc_forward_handlerIf the search fails, the method is not assembly, but search_objc_forward_handlerFound this method calledobjc_defaultForwardHandler
  • The return valueobjc_defaultForwardHandlerThe above error reported a typo

A chance is given at resolveMethod_locked before error handling is entered

Object method dynamic resolution

  • View macro definition enumeration values

  • Enter into this method judgment

  • Check behavior equals 3

  • If the result is 2 greater than 0, the method is entered

  • After entering, the value of behavior is changed to 1. If we judge it again, it will be 0 and no longer enter. Therefore, we conclude that this judgment will only go once. Is a singleton.

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked(a);ASSERT(cls->isRealized());

    runtimeLock.unlock(a);// programmer you are not stupid without this method - imp-nil
    // Be friendly
    // Give you a chance to save the earth -> IMP
    // Determine whether the CLS class is a metaclass, if the class specification indicates that the instance method is called
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);  // The object method enters
    } 
    else {
        // If it is a metaclass, the class method is called
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls); // Class method entry
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);  // Since the class is actually an object in the metaclass, the final call to the parent class goes into NSObject, so call the object method's processing implementation method again.}}// The imp that corresponds to sel fast lookup and sel slow lookup is actually retrieved from the cache, because it has been cached previously
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}



Copy the code

The object method goes to resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked(a);ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // 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 = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass()?'+' : The '-', 
                         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()?'+' : The '-', 
                         cls->nameForLogging(), sel_getName(sel)); }}}Copy the code
  • 1.SEL resolve_sel = @selector(resolveInstanceMethod:);Get this method and save it in a variable
  • 2. BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, resolve_sel, sel);System active callresolveInstanceMethodMethod and return BOOL
  • Objc_msgSend (resolveInstanceMethod: @selector(XXX))
  • 4. The CLS metaclass, instead of inst, calls the “- (void) “object method, equivalent to the + (void) class method. That’s why resolveInstanceMethod writes class methods in its class.
  • 5. When we overwrite the resolveInstanceMethod method, this method will be stored in the list of methods during system memory loading. Objc_msgSend is the message sending process, which will quickly search the cache, and then slowly search the list of methods. Now that I’ve overridden this method it must be able to find and execute.
  • 6. After execution, bind the name of an unimplemented method to the address of an implemented method in the method list. This is the result of this processing error.

Override the resolveInstanceMethod method

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    // resolveInstanceMethod: lgteacher-say666
    // handle sel -> imp
    if (sel == @selector(say666)) {
        IMP sayNBImp     = class_getMethodImplementation(self.@selector(sayNB));
        Method method    = class_getInstanceMethod(self.@selector(sayNB));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, sayNBImp, type);
    }
    
    NSLog(@"resolveInstanceMethod :%@-%@".self.NSStringFromSelector(sel));

    return [super resolveInstanceMethod:sel];
}
Copy the code

  • If there is NO override, the system method is called, and NO is returned by default.

resolveClassMethod

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

    if (!lookUpImpOrNilTryCache(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 = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass()?'+' : The '-', 
                         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()?'+' : The '-', 
                         cls->nameForLogging(), sel_getName(sel)); }}}Copy the code

ResolveClassMethod The object method of a metaclass

// the object method of the metaclass
+ (BOOL)resolveClassMethod:(SEL)sel{
   
    NSLog(@"resolveClassMethod :%@-%@".self.NSStringFromSelector(sel));
    
    if (sel == @selector(sayHappy)) {
        IMP sayNBImp     = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC));
        Method method    = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type);
    }

    return [super resolveClassMethod:sel];
}

Copy the code

You can also unify and create categories to handle, to handle dynamic resolutions for all classes

#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface NSObject (LG) @end NS_ASSUME_NONNULL_END #import "NSObject+LG.h" #import <objc/message.h> @implementation NSObject (LG) - (void)sayNB{ NSLog(@"%@ - %s",self , __func__); } + (void)sayKC{ NSLog(@"%@ - %s",self , __func__); } #pragma clang diagnostic ignore "-wundeclared -selector" + (BOOL)resolveInstanceMethod:(SEL) SEL {// resolveInstanceMethod: lgteacher-say666 NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel)); if (sel == @selector(say666)) { IMP sayNBImp = class_getMethodImplementation(self, @selector(sayNB)); Method method = class_getInstanceMethod(self, @selector(sayNB)); const char *type = method_getTypeEncoding(method); return class_addMethod(self, sel, sayNBImp, type); }else if (sel == @selector(sayHappy)) { IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC)); Method method = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC)); const char *type = method_getTypeEncoding(method); return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type); } return NO; } @endCopy the code

Finally, lookUpImpOrNilTryCache is called when the dynamic resolution is processed

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return_lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL); } _lookUpImpTryCache is also a fast lookup process and a slow lookup process. If you write the resolveInstanceMethod method and add it correctly, you will find the method address.Copy the code
  • Imp goes here and prints a piece of information

  • This last

resolveInstanceMethod(inst, sel, cls);First enter this method after processing is complete, calllookUpImpOrForwardTryCacheThis method is actually called_lookUpImpTryCacheThis is actually the message sending mechanism flow, first look for the cache method, and then calllookUpImpOrForwardMethods.

//#pragma clang diagnostic pop

/ * *

say666 () sel -> imp

1: An opportunity given by Apple 2: Global all methods can not find whether we can monitor 3: Lg_model_traffic -> lg_home_didClickDetail -> pop Home -> sending didClickDetail -> Monitoring BUG 4: Runtime -> 5: AOP-OOP The path to iOS AOP programming 6: Object division is very clear – redundant code -> Extract -> Common classes (strong dependencies – Strong coupling) 7: Non-intrusive – Dynamically injected code – Cut method cut class 8: 9: Message forwarding process: fast + slow forwarding

* /

  • 1. It may write a non-existent method to report a crash, which is bad for the user experience. The system has given it a chance to process
  • 2. If the method cannot be found, it must be taken onceresolveInstanceMethod
  • 3. Use AOP programming to send logging crash messages for error handling