preface

In previous articles on iOS underlying Principles –RunTime objc_msgSend and iOS Underlying Principles — slow lookup processes for methods, we have analyzed the nature of methods: message sending, and we have also analyzed fast lookup and slow lookup processes for message sending. Is there really no remedy when we find that the method is not implemented when fast lookup and slow lookup are not found? Direct collapse? That’s not apple . Below 👇 we begin to study if the method is not implemented, what opportunities will  give us?

Dynamic method resolution

Actually in the last articleUnderlying principles of iOS – slow lookup flow of methodsWe have analyzed why methods fail to be implemented, and have briefly mentioned thisDynamic method resolution.Also analyzed, the judgment condition here is equivalent to a singleton, onlyA ohThat means apple is only giving you hereA second chanceTo save the last shred of dignity.

Text summary process

  1. Determines whether CLS is a metaclass, and if it is a class (non-metaclass)resolveInstanceMethodDynamic method resolution for instance methods.
  2. If it is a metaclass, it is called firstresolveClassMethodDynamic method resolution for class methods. If it is not found in the metaclass, the metaclass’sresolveInstanceMethodInstance method dynamic resolution. (because theClass methodinYuan classIs also toInstance method existsNot per se+and-Points).
  3. ifDynamic method resolution, points the implementation to another method, and proceedslookUpImpOrForwardTryCacheMethod to slow down the search process.

Dynamic methods for parsing small flowcharts

Study on resolveInstanceMethod method

Let’s take a look at the source code for this method.

  • Define aresolveInstanceMethodSEL, the name isresolve_sel
  • throughlookUpImpOrNilTryCacheMethod to find if it is implementedresolveInstanceMethodThis method, if not implemented, returns directly. If implemented, sendresolveInstanceMethodThe message.
  • throughlookUpImpOrNilTryCacheMethod, again for method lookup.

ResolveClassMethod method exploration

  • resolveClassMethodinNSobjectAs long as the metaclass initialization is done, the purpose is to cache in the metaclass
  • imp = lookUpImpOrNilTryCache(inst, sel, cls)The cacheselThe correspondingimp, no matterimpHave dynamic add, if not found, here is allforward_imp

Probe into lookUpImpOrNilTryCache method

Let’s take a look at the source code and see that this is just another method being called_lookUpImpTryCache.We look at the_lookUpImpTryCacheSource code for the method.

  1. Cache lookup
  • Look it up in the cacheselThe correspondingimp
  • impIf yes, the system jumpsdoneprocess
  • If there is a shared cache for the underlying system library, if there is no query in the cacheimp, enter the slow search process
  1. Slow search process
  • In the slow search process,behavior= 44 & 2 = 0Dynamic method resolution is not entered again, so there is no loop
  1. doneprocess
  • doneProcess: (behavior & LOOKUP_NIL) andimp = _objc_msgForward_impcache, if IMP isforward_imp, return directlynil, if not, returnimp.

Dynamic method resolutionThe measured

1. First let’s add the YSHPerson class+(BOOL)resolveInstanceMethod:(SEL)sel 2. We found that the call was made before the crashtwoResloveInstanceMethod method 3. Why is it called twiceresolveInstanceMethodMethod?The first is dynamic method resolution, the system automatically toresolveInstanceMethodSend a message, so what is called the second time? 👇 We analyze a wave through LLDB.

After the first call, we analyzed the call stack through BT and found that the method was slow to find the dynamic resolution of the process.

After the second invocation, by the underlying system libraryCoreFoundationTuned upNSObject(NSObject) methodSignatureForSelector:After, will againEnter dynamic resolution.

Dynamically add the sayHappy method

We add the following code and find that the program no longer crashes and can call the sayNB method

  • The discoveryresolveInstanceMethodOnly called once, because it is added dynamicallysayHappymethodslookUpImpOrForwardTryCacheDirect access toimp, directly callimp, the search process ends
  • Call flow:resolveMethod_locked–>resolveInstanceMethod–>resolveInstanceMethod–>lookUpImpOrNilTryCache(inst, sel, cls)–>lookUpImpOrForwardTryCache— > callimp

Class method

Class methodLike the instance methods, they can also be overriddenresolveClassMethodClass method to resolve the crash problem, i.eLGTeacherClass, and willsayHappyClass method implementationsayKC

Override of the resolveClassMethod class method. Note: The CLS passed in is no longer a class but a metaclass, and you can get the metaclass of the class through the objc_getMetaClass method because the class methods are instance methods in the metaclass

Dynamic method resolution summary

The way we’ve just done it is to rewrite each class individually, in a project, let’s say hundreds or thousands of classes, which would be exhausting. Is there a better way? We found that there are two method search paths through the method slow search process before:

  • Instance methods:Class --> parent --> root --> nil
  • Methods:Metaclass --> root metaclass --> root class --> nil

They always find itThe root class is NSObjectThen we can passNSObject Adds a classThe way toRealize unified processingAnd because the class method lookup, in its inheritance chain, lookup is also the instance method, so you can put the unified treatment of instance method and class method inresolveInstanceMethodIn the method, as follows:The implementation of this way, just with the source code for the method of class processing logic is consistent, that is, perfect exposition of why to call the class method dynamic method resolution, but also call the object method dynamic method resolution, the root cause or becauseClass methods also exist as instantiated methods in metaclasses.

Disadvantages: There are some other problems with this method, such as system methods can also be changed, so we can name the prefix of the method name for the custom class, determine whether the method is a custom method according to the prefix, and then handle the custom method in the same way, which is convenient for business differences. advantages

  • It can deal with the problem of method crash uniformly, and the method crash can be reported to the server or pop to the home page, thus improving user experience
  • If there are different modules in the project and the method name prefix is different, the service can be distinguished.

I’m going to expand hereAOPandOOP

  • OOP: In fact, it is the encapsulation of the properties and behaviors of the object. The same functions are extracted and encapsulated separately, with high coupling and strong dependence.
  • AOPThe above method of adding in categories is actually a manifestation of AOP. Is to deal with a certain step and stage, from which the extraction of the section, there is repeated operation behavior, AOP can be extracted, the use of dynamic proxy, the unified maintenance of program functions, dependence is small, the coupling degree is small, aloneAOPThe removal of extracted functionality also has no impact on the main code.AOPMore like a three-dimensional vertical axis, each class in the plane has a common logical passageAOPConcatenated, the various classes in the plane itself have no relation.

Message Forwarding Process

Take the liberty of combining the two blogs into one post.

Method if in the fast + slow search and through the dynamic method resolution still did not find the real method implementation IMP, at this time the dynamic method resolution throw IMP = forward_IMP into the message forwarding process. However, we did not find the implementation of message forwarding by searching the source code. 👇 below. Let me introduce two ways.

  • throughinstrumentObjcMessageSendsMode Displays logs about sending messages
  • throughHopper/IDA decompiling

Log auxiliary

throughlookUpImpOrForward –> log_and_fill_cache –> logMessageSend.Enter the logMessageSendSee the source code implementation.And the implementer we analyze has to have a value, sologMessageSendIf I could call it,objcMsgLogEnabledIt must beYES. /tmp/msgSendsIs the sandbox path for storing logs. After this function is enabled, you can directly go to the sandbox path to obtain files. And what we found is thatobjcMsgLogEnabledThe default isfalseSo you need to find where to assign.Global search, we found that there is only one place to assign, and that is throughinstrumentObjcMessageSendstoobjcMsgLogEnabledAssignment, so declare it where log information is neededinstrumentObjcMessageSends.

/ / slow for extern void instrumentObjcMessageSends (BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *person = [LGPerson alloc]; instrumentObjcMessageSends(YES); [LGPerson say666]; instrumentObjcMessageSends(NO); } return 0; }Copy the code

Run the code and go to/tmp/msgSendsCatalog, found theremsgSendsAt the beginning of the log file, opening it found that before the crash, the following methods were executed:

  • Dynamic method resolution: resolveInstanceMethod method

  • News fast forwarding: forwardingTargetForSelector method

  • The news slowly forward: methodSignatureForSelector + resolveInvocation

Fast forward

forwardingTargetForSelector

Open theXcode.command + shift +0.Notice that this is 0 instead of oAnd then search forforwardingTargetForSelector forwardingTargetForSelectorThe meaning is to return an unrecognized messageredirectSpecify an object to receive the message

Fast forward code measured

/ / slow for extern void instrumentObjcMessageSends (BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *person = [LGPerson alloc]; instrumentObjcMessageSends(YES); [person sayHello]; instrumentObjcMessageSends(NO); NSLog(@"Hello, World!" ); } return 0; }Copy the code
-(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(sayHello)) {
        return [[LGStudent alloc] init];
    }
    return  [super forwardingTargetForSelector:aSelector];
}
Copy the code
@implementation LGStudent

- (void)sayHello{
    NSLog(@"%s",__func__);
}
Copy the code

Based on the code above, let’s test to see if the code still crashes.

Based on the printed results, we found that the project did not crash.

  • In other words, ifforwardingTargetForSelectorMethod returnsMessage receiverIf the receiver of the message is still not found, it enters the search process of another method. If the receiver of the message is still not found, it will also report an error crash.
  • If the returnnil“, the slow message forwarding is displayed

Slowly forward

Slowly forwardmethodSignatureForSelectorIs also the method to find the last process, as the saying goes, but three, if your system has a method error, the system will give a firstDynamic method resolutionGive me another chanceFast forwardIf you don’t grasp the opportunity, the system will give you one last timeSlowly forwardThe opportunity. If this last chance is not grasped, then just saysorry, crash and flash back.

We follow the exploration process of fast forwarding and also searchmethodSignatureForSelector. methodSignatureForSelectorThe essence of is to return oneNSMethodSignatureObject that contains a description of the method identified by the given selector.methodSignatureForSelectorGeneral collocation andforwardInvocationUse, ifmethodSignatureForSelectorThe method returns anilI’m not gonna callforwardInvocation.

Slow forwarding code measurement

Below 👇 we analyze through the code.

  1. We are inmethodSignatureForSelectorMethod, just return nil, and let’s see what happens.
@implementation LGPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"---%@---",anInvocation);
}
Copy the code

Let’s take a look at the print, and it crashes.2. We return oneNSMethodSignatureObject, let’s see what happens.

@implementation LGPerson

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

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"---%@---%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));

}
Copy the code

If we look at the print, it doesn’t crash, it printsforwardInvocationThe content of a method.

From this we find that ifmethodSignatureForSelectorThe return value ofNSMethodSignatureObject is calledforwardInvocationProcess. Among themanInvocationSave theNSMethodSignatureSignature information, as well as the method signature of the target methodselAnd the recipient of the method.

It is also possible to handle the Invocation transaction, as shown below, by modifying the Invocation target to [LGStudent alloc], calling [anStudent Invoke], That is, a call to the sayHello instance method of the LGPerson class will call LGStudent’s sayHello method.

@implementation LGPerson

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

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"---%@---%@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
    anInvocation.target = [LGStudent alloc];
    [anInvocation invoke];
}
Copy the code

The following results are printed: 👇 :Therefore, from the above, no matter inforwardInvocationIn the methodWhether to handle the InvocationTransactions, programsWon't break down.

Summary of flow chart

Based on this analysis of dynamic method resolution and message forwarding, we can draw a flow chart like this.