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 oh
That means apple is only giving you hereA second chance
To save the last shred of dignity.
Text summary process
- Determines whether CLS is a metaclass, and if it is a class (non-metaclass)
resolveInstanceMethod
Dynamic method resolution for instance methods. - If it is a metaclass, it is called first
resolveClassMethod
Dynamic method resolution for class methods. If it is not found in the metaclass, the metaclass’sresolveInstanceMethod
Instance method dynamic resolution. (because theClass method
inYuan class
Is also toInstance method exists
Not per se+
and-
Points). - if
Dynamic method resolution
, points the implementation to another method, and proceedslookUpImpOrForwardTryCache
Method 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 a
resolveInstanceMethod
SEL, the name isresolve_sel
- through
lookUpImpOrNilTryCache
Method to find if it is implementedresolveInstanceMethod
This method, if not implemented, returns directly. If implemented, sendresolveInstanceMethod
The message. - through
lookUpImpOrNilTryCache
Method, again for method lookup.
ResolveClassMethod method exploration
resolveClassMethod
inNSobject
As long as the metaclass initialization is done, the purpose is to cache in the metaclassimp = lookUpImpOrNilTryCache(inst, sel, cls)
The cachesel
The correspondingimp
, no matterimp
Have 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_lookUpImpTryCache
Source code for the method.
Cache lookup
- Look it up in the cache
sel
The correspondingimp
imp
If yes, the system jumpsdone
process- If there is a shared cache for the underlying system library, if there is no query in the cache
imp
, enter the slow search process
Slow search process
- In the slow search process,
behavior
=4
,4
&2
=0
Dynamic method resolution is not entered again, so there is no loop
done
process
done
Process: (behavior
&LOOKUP_NIL
) andimp
=_objc_msgForward_impcache
, if IMP isforward_imp
, return directlynil
, if not, returnimp
.
Dynamic method resolution
The measured
1. First let’s add the YSHPerson class+(BOOL)resolveInstanceMethod:(SEL)sel
2. We found that the call was made before the crashtwo
ResloveInstanceMethod method 3. Why is it called twiceresolveInstanceMethod
Method?The first is dynamic method resolution, the system automatically toresolveInstanceMethod
Send 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 libraryCoreFoundation
Tuned 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 discovery
resolveInstanceMethod
Only called once, because it is added dynamicallysayHappy
methodslookUpImpOrForwardTryCache
Direct access toimp
, directly callimp
, the search process ends - Call flow:
resolveMethod_locked
–>resolveInstanceMethod
–>resolveInstanceMethod
–>lookUpImpOrNilTryCache(inst, sel, cls)
–>lookUpImpOrForwardTryCache
— > callimp
Class method
Class method
Like the instance methods, they can also be overriddenresolveClassMethod
Class method to resolve the crash problem, i.eLGTeacher
Class, and willsayHappy
Class 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 NSObject
Then we can passNSObject Adds a class
The way toRealize unified processing
And 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 inresolveInstanceMethod
In 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 hereAOP
andOOP
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.AOP
The 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, aloneAOP
The removal of extracted functionality also has no impact on the main code.AOP
More like a three-dimensional vertical axis, each class in the plane has a common logical passageAOP
Concatenated, 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.
- through
instrumentObjcMessageSends
Mode Displays logs about sending messages - through
Hopper/IDA decompiling
Log auxiliary
throughlookUpImpOrForward
–> log_and_fill_cache
–> logMessageSend
.Enter the logMessageSend
See the source code implementation.And the implementer we analyze has to have a value, sologMessageSend
If I could call it,objcMsgLogEnabled
It must beYES
. /tmp/msgSends
Is 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 thatobjcMsgLogEnabled
The default isfalse
So you need to find where to assign.Global search, we found that there is only one place to assign, and that is throughinstrumentObjcMessageSends
toobjcMsgLogEnabled
Assignment, 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/msgSends
Catalog, found theremsgSends
At 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 o
And then search forforwardingTargetForSelector
forwardingTargetForSelector
The meaning is to return an unrecognized messageredirect
Specify 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, if
forwardingTargetForSelector
Method returnsMessage receiver
If 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 return
nil
“, the slow message forwarding is displayed
Slowly forward
Slowly forwardmethodSignatureForSelector
Is 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 resolution
Give me another chanceFast forward
If you don’t grasp the opportunity, the system will give you one last timeSlowly forward
The 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
. methodSignatureForSelector
The essence of is to return oneNSMethodSignature
Object that contains a description of the method identified by the given selector.methodSignatureForSelector
General collocation andforwardInvocation
Use, ifmethodSignatureForSelector
The method returns anil
I’m not gonna callforwardInvocation
.
Slow forwarding code measurement
Below 👇 we analyze through the code.
- We are in
methodSignatureForSelector
Method, 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 oneNSMethodSignature
Object, 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 printsforwardInvocation
The content of a method.
From this we find that ifmethodSignatureForSelector
The return value ofNSMethodSignature
Object is calledforwardInvocation
Process. Among themanInvocation
Save theNSMethodSignature
Signature information, as well as the method signature of the target methodsel
And 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 inforwardInvocation
In the methodWhether to handle the Invocation
Transactions, 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.