Summary of basic principles of iOS

primers

In the previous two articles objc_msgSend Process analysis Fast Lookup and objc_msgSend process analysis Slow lookup, apple analyzed fast lookup and slow lookup of objc_msgSend respectively. In the case of failing to find a way to implement either method, Apple gave two suggestions

  • Dynamic method resolutionIf the slow lookup process is not found, a dynamic method resolution is executed
  • forward: If the dynamic method resolution still does not find an implementation, the message is forwarded

If neither of these suggestions does anything, it will report an unimplemented crash of our common methods in daily development, as follows

Define LGPerson class where the sayMaster instance method and sayHappy class method are not implemented

Call LGPerson instance sayMaster and class sayHappy respectively in main, and run the program, both of which will give an error indicating that the method is not implemented, as shown below

  • Error result from calling instance method sayMaster

  • Error result from calling class method sayHappy

Method not implemented error source code

According to the source code of a slow search, we found that the error is finally to the __objc_msgForward_impcache method, the following is the source of the error process

STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache

//👇
ENTRY __objc_msgForward

adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
    
END_ENTRY __objc_msgForward
Copy the code

Assembly implementation to find __objC_forward_handler, and did not find, in the source code to remove an underscore for global search _objC_forward_handler, there is the following implementation, the essence is to call the objc_defaultForwardHandler method

Looking atobjc_defaultForwardHandlerDoes this look familiar? Here are the most common mistakes we make in daily development:Failed to implement the function, run the program, crash times error message.

Now, let’s talk about how to prevent unimplemented crashes of methods before they crash.

Three method lookups to save the opportunity

Based on apple’s two suggestions, we have three salvage opportunities:

  • First chance dynamic method resolution

  • Message Forwarding Process

    • 【 SECOND chance 】Fast forward
    • 【 Third Chance 】Slowly forward

First chance dynamic method resolution

In the slow search process did not find the method implementation, first will try a dynamic method resolution, its source code implementation is as follows:

It is mainly divided into the following steps

  • Determine whether a class is a metaclass

    • If it isclass, the implementation ofInstance methodsDynamic method resolutionresolveInstanceMethod
    • If it isThe metaclass, the implementation ofClass methodDynamic method resolutionresolveClassMethod, if in a metaclassCould not findOr foremptyWhile the,The metaclasstheInstance methodsDynamic method resolutionresolveInstanceMethodIn the search, mainly becauseClass methods are instance methods in metaclasses, so you also need to look for dynamic method resolutions for instance methods in the metaclass
  • If the dynamic method resolution points its implementation to another method, the lookup continues for the specified IMP, that is, the lookUpImpOrForward process continues slowly

The process is as follows

Instance methods

ResolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod

It is mainly divided into the following steps:

  • Before sending a resolveInstanceMethod message, you need to look for an implementation of this method in the CLS class. LookUpImpOrNilTryCache is used to look up the resolveInstanceMethod in the lookUpImpOrForward slow lookup process

    • If not, return directly
    • If yes, send itresolveInstanceMethodThe message
  • The lookUpImpOrNilTryCache method is used to look up the instance method in the lookUpImpOrForward slow lookup process

The resolveInstanceMethod dynamic resolution method prints “come” twice. You can see this by looking at the stack information

  • [first dynamic resolution] The first “coming” is the dynamic method resolution when searching for say666

  • Second second dynamic resolution 】 【 “come” is slowly forward process invokes the CoreFoundation framework of NSObject (NSObject) methodSignatureForSelector: after, will once again into the dynamic resolution

Note: Please refer to the problem exploration at the end of the article for detailed analysis process

Class method

For class methods, similar to instance methods, we can also solve the crash problem by overriding the resolveClassMethod class method in LGPerson and pointing the implementation of the sayNB class method to lgClassMethod

The CLS passed in is not a class, but a metaclass. You can get the metaclass of the class through the objc_getMetaClass method, because the class method is an instance method in the metaclass

To optimize the

Is there a better way to do this once and for all? In fact, it can be found that there are two search paths through the method slow search process

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

What they all have in common is if they don’t find it, they all go to the root class which is NSObject, so can we integrate these two methods together? The answer is yes, you can do this by adding classes to NSObject, and because class methods look up, in their inheritance chain, also look up instance methods, you can put the unified handling of instance methods and class methods in the resolveInstanceMethod method, as shown below

+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); IMP imp = class_getMethodImplementation(self, @selector(sayMaster)); Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster)); const char *type = method_getTypeEncoding(sayMethod); return class_addMethod(self, sel, imp, type); }else if (sel == @selector(sayNB)) {NSLog(@"%@ ", NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); const char *type = method_getTypeEncoding(lgClassMethod); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return NO; }Copy the code

The implementation of this way, just with the source code for the method of class processing logic is consistent, that is, the perfect explanation why to call the method of class dynamic method resolution, but also call the object method dynamic method resolution, the fundamental reason is the method of class in the metaclass instance method.

Above this kind of writing, of course, there will be other problems, such as system method also can be changed, for this, can be optimized, namely we can according to the custom class method unified method name prefix, judging by the prefix is a custom method, and then unified handling custom methods, for example can pop before collapse to the home page, It is mainly used for anti-crash processing of APP online to improve user experience.

Message Forwarding Process

In the slow process to find out, we know that if the fast + slow didn’t find method, dynamic method resolution also not line, using the message forwarding, however, we haven’t found searched source message forwarding relevant source, can through the following ways to get to know, go which methods before collapse method calls

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

Through instrumentObjcMessageSends

  • Through lookUpImpOrForward – > log_and_fill_cache – > logMessageSend, found at the bottom of the logMessageSend source instrumentObjcMessageSends source code to achieve, so, In the main call instrumentObjcMessageSends print method call log information, has the following two preparations

    • 1. Open theobjcMsgLogEnabledSwitch, that is, callinstrumentObjcMessageSendsMethod when passed inYES
    • 2, inmainThrough theexternThe statementinstrumentObjcMessageSendsmethods
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

The logMessageSend source code shows that the sent messages are stored in the/TMP /msgSends directory, as shown below

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

  • Two dynamic method resolutions: the resolveInstanceMethod method

  • Two news fast forwarding: forwardingTargetForSelector method

  • Two news slowly forward: methodSignatureForSelector + resolveInvocation

Decompilation via Hopper /IDA

Hopper and IDA are tools that help us statically analyze visibility files, disassemble executable files into pseudocode, control flow diagrams, etc. Take Hopper as an example. (Note: Hopper Advanced is a paid software, and the demo is sufficient for simpler disassembly needs.)

  • Run program crash, view stack information

Found ___forwarding___ from CoreFoundation

Through the Image List, read the entire image file, and then search for CoreFoundation to see the path to its executable

Follow the file path to the CoreFoundation executable

Open Hopper, select Try the Demo, and then drag the executable file from the previous step into Hopper to disassemble. Select x86(64 bits)

Search for __forwarding_prep_0___ through the search box on the left, then select the pseudocode

  • The following is a__forwarding_prep_0___Assembler pseudocode, jump to___forwarding___

Here is ___forwarding___ pseudo code realization, first is to look at whether forwardingTargetForSelector method, if there is no response, jump to loc_6459b namely fast forwarding no response, forward into a slow process,

Jump to loc_6459b, below determine whether response methodSignatureForSelector method,

  • ifThere is no responseJump toloc_6490b, an error message is displayed
  • If you getmethodSignatureForSelectortheThe method signatureNil, which is also an error

If methodSignatureForSelector the return value is not null, then in forwardInvocation approach to deal with invocation

Therefore, through the above two search methods can be verified, there are three methods of message forwarding

  • Fast forward forwardingTargetForSelector 】

  • [Slow forwarding]

    • methodSignatureForSelector
    • forwardInvocation

Therefore, in summary, the overall process of message forwarding is as follows

The processing of message forwarding is divided into two parts:

  • “Fast forward” when slow lookup, resolution and dynamic methods were not find implementation, for message forwarding, the first is a quick message forwarding, namely forwardingTargetForSelector method

    • If the returnMessage receiverIf it is still not found in the message receiver, it enters the search process of another method
    • If the returnnil“, the slow message forwarding is displayed
  • Perform to slow forward 】 【 methodSignatureForSelector method

    • If the returnedThe method signaturefornilThe directCollapse of the error
    • If the returned method is signedDon't is nilAnd went toforwardInvocationMethod, the Invocation transaction is handled and no error is reported if it is not

Second chance: Quick retweet

According to the collapse of the above problem, if the dynamic method resolution but could not find, you need to rewrite forwardingTargetForSelector method in LGPerson, Specify the recipient of LGPerson’s instance method as LGStudent’s object (LGStudent class has a concrete implementation of sayInstanceMethod), as shown below

- (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); Return [LGStudent alloc]; return [LGStudent alloc]; }Copy the code

The result is as follows

You can also call the method of the parent class without specifying the receiver. If no message is found, an error is reported

[Third chance] Slow forwarding

For a second chance in fast forward or not found, is in the final of a saving opportunity, namely in the LGPerson rewrite methodSignatureForSelector, as shown below

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

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

The forwardInvocation method does not handle the invocation and does not crash

It is also possible to handle the Invocation transaction, as shown below, by modifying the Invocation target to [LGStudent alloc], The Invocation of LGPerson’s sayInstanceMethod will invoke LGStudent’s sayInstanceMethod

The print result is as follows

So, from the above, the program does not crash whether the forwardInvocation method handles the invocation transaction or not.

“Why are dynamic method resolutions executed twice?” To explore problems

The dynamic method resolution method mentioned in the previous article has been implemented twice, with the following two ways of analysis

Explore god’s perspective

In the slow lookup process, The resolveInstanceMethod method is executed via lookUpImpOrForward –> resolveMethod_locked –> ResolveInstanceMethod goes to the resolveInstanceMethod source code, which is triggered by sending the resolve_sel message, as shown below

IMP IMP = lookUpImpOrNilTryCache(INST, SEL, CLS); Add a breakpoint, print the stack through Bt and see what’s going on

  • inresolveInstanceMethodIn the methodIMP imp = lookUpImpOrNilTryCache(inst, sel, cls);Add a breakpoint and run the program until the first time"Come", using BTFirst dynamic method resolutionIs the stack information of selsayNB

  • Continue until the second time"Coming" print, looking at the stack information, in the second, we can see that is throughCoreFoundationthe-[NSObject(NSObject) methodSignatureForSelector:]Method, and then throughclass_getInstanceMethodGoing into dynamic method resolution again,

So with the stack information from the last step, we need to look at what’s going on in CoreFoundation? Through the Hopper of the disassembly CoreFoundation executable file, see methodSignatureForSelector method of pseudo code

By entering ___methodDescriptionForSelector methodSignatureForSelector pseudo code implementation

Enter ___methodDescriptionForSelector pseudo code realization, combine compiled stack print, you can see, In ___methodDescriptionForSelector this method calls the class_getInstanceMethod objc4-781

  • Search the source code in objCclass_getInstanceMethod, the source code implementation is shown below

This can be through code debugging to verify, as shown below, add a breakpoint at class_getInstanceMethod method, after execution methodSignatureForSelector method, returned to the signature, illustrate the method signature is effective, Apple gives the developer a chance to invocation again before the Invocation goes to class_getInstanceMethod, the method query say666 again, and then the dynamic method resolution again

Therefore, the above analysis also confirms the reason why the resolveInstanceMethod method was executed twice

Exploration without a God’s perspective

If there is no God perspective, we can also use the code to deduce where the dynamic method resolution is called again

  • In the LGPerson rewriteresolveInstanceMethodMethod, and plusclass_addMethodOperating theAssignment IMPAt this time,resolveInstanceMethodWill you go twice?