Code word is not easy, ask a wave of thumbs up, attention. Thank you!!
preface
OC method lookup if the fast lookup process and slow lookup process do not find the corresponding IMP, and dynamic method resolution does not dynamically add the corresponding IMP, will enter the message forwarding process. Having already analyzed the objc_msgSend fast lookup process, the objc_msgSend slow lookup process, and the objc_msgSend dynamic method resolution of the iOS Runtime, this article explores the message forwarding process.
The preparatory work
- Objc4-818.2 – the source code.
- CoreFoundation source
- Disassembly tool
Hopper
andida
.
1: exploring the message forwarding process
1.1: Exploration of the message forwarding source process
// Only part of the code is cut because of length
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// Specify imp for message forwarding
constIMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; .for (...) {
...
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
(CLS ->supercls->nil) (CLS ->supercls->nil
// If the dynamic method resolution does not work, the message is forwarded
imp = forward_imp;
break; }... }... }Copy the code
- According to the
lookUpImpOrForward
The source code of the function is known for message forwardingimp
The default is_objc_msgForward_impcache
, global search, inC++
Source code did not find the relevant implementation, according to the experience of exploring before, into the assembly fileobjc-msg-arm64.s
Find the relevant source code__objc_msgForward_impcache
.
__objc_msgForward_impcache
jump__objc_msgForward
And then it’s evaluated__objc_forward_handler
depositp17
, and finally callp17
. Global search__objc_forward_handler
, the implementation part was not found, as a rule of thumb remove the underline search, inC++
fileobjc-runtime.mm
Found the relevant source code.
-
Void *_objc_forward_handler = (void*)objc_defaultForwardHandler The object c_defaultForwardHandler function pointer contains the classic unrecognized Selector sent to XXX error.
-
Recognized selector sent to; recognized selector sent to; recognized selector sent to; recognized selector sent to; recognized selector sent to; recognized selector sent to Comments are also shown to be replaced by CoreFoundation.
-
Until you notice the objc_setForwardHandler function, and interrupt the output of the FWD argument passed in, The program runs and assigns _objc_forward_handler to the CoreFoundation function _CF_forwarding_prep_0.
1.2: Case verification
Create SDSingleDog class, declare -girlfriend and +getMarried, neither of them can be implemented, run code, single dog starts looking for girlfriend, can’t find, he will break down.
@interface SDSingleDog : NSObject
- (void)girlfriend;
+ (void)getMarried;
@end
@implementation SDSingleDog
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
SDSingleDog *singleDog = [SDSingleDog alloc];
[singleDog girlfriend];
NSLog(@"Hello, World!");
}
return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 12 16:30:02.439981+0800 KCObjcBuild[4559:142606] -[SDSingleDog girlfriend]: unrecognized selector sent to instance 0x1018adac0
Copy the code
Running result diagram:
Unrecognized Selector sent to XXX
- Before the exception is thrown
_CF_forwarding_prep_0
Functions and___forwarding___
Function is called according toObjc_msgSend dynamic method resolution for iOS RuntimeGuess, this should be the relevant process triggered by message forwarding. _CF_forwarding_prep_0
The function,___forwarding___
Functions anddoesNotRecognizeSelector
Methods forCoreFoundation
Library source code.
Since the CoreFoundation source code is not fully open source, Above the iOS objc_msgSend dynamic method the runtime resolution we already know that by means of auxiliary debug log message forwarding relevant methods forwardingTargetForSelector: and methodSignatureForSelector: We will expand the analysis of message forwarding from here.
2: fast forwarding
2.1: Fast forwarding API analysis
Through log auxiliary function know forwardingTargetForSelector: method, but the default implementation simply returns nil in the source code.
In order to quickly understand this method, at the same time hold down the command + shift + 0, evocation development documents, search forwardingTargetForSelector: method, to check the related documents.
According to development documentation forwardingTargetForSelector: the method is to give the unrecognized message returns a shall, first of all, pointing to the object, this object to receive the news (is nil and the self), if the object cannot handle, will also be recursive search, the parent class Check to see if the parent on the inheritance chain can handle it.
2.2: Case verification fast forwarding
Declare the SDSingleDog and XJStudent classes, SDSingleDog states the – (void)girlfriend method, but does not implement it, XJStudent implements the – (void)girlfriend method, The – (void)girlfriend method is called from the object of the SDSingleDog class in the main function (SDSingleDog and XJStudent may not be inherited).
@implementation SDSingleDog
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (@selector(girlfriend) == aSelector) {
return [[XJStudent alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
@implementation XJStudent
- (void)girlfriend
{
NSLog(@"%s, The student have girlfriend", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
SDSingleDog *singleDog = [SDSingleDog alloc];
[singleDog girlfriend];
}
return 0; } * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 13 00:25:55.525656+0800 KCObjcBuild[4424:102160] -[XJStudent girlfriend], The student have girlfriend
Copy the code
- The message was forwarded successfully
XJStudent
Class.SDSingleDog
Classes andXJStudent
Class, but after the redirection, the program runs normally.
This method gives the object the opportunity to redirect the unknown messages sent to it before the more expensive regular forwardInvocation: take over. This is useful when you just want to redirect the message to another object, and can be an order of magnitude faster than regular forwarding, so it is also known as fast forwarding.
3: indicates slow forwarding
3.1: Slow forwarding API analysis
If the rapid message forwarding is not implemented, then the slow (also called regular) invocation will be used, again holding command + shift + 0 and calling the development document and looking for the forwardInvocation.
According to the document to the response object itself can’t identify method, also have to rewrite methodSignatureForSelector: besides forwardInvocation:. Forwarding message mechanism using from methodSignatureForSelector: the information to create the NSInvocation object is forwarding. Rewrite methodSignatureForSelector: method must provide the appropriate method for the given selector signature NSMethodSignature, can be set in advance a method signature, also can be to another object request a method signature. If methodSignatureForSelector: method returns nil, wouldn’t call forwardInvocation:.
3.2: Case verification of slow forwarding
SDSingleDog class methodSignatureForSelector: return to/super methodSignatureForSelector: aSelector method directly, namely, nil.
@implementation SDSingleDog
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"@ ___ ___ %", anInvocation); } @ the end * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 13 11:51:04.491402+0800 KCObjcBuild[1693:50929] -[SDSingleDog girlfriend]: unrecognized selector sent to instance 0x101931220
Copy the code
methodSignatureForSelector:
Method returns directlynil
, the slow forwarding terminates, the program crashes and is thrownunrecognized selector sent to xxx
The exception.
@implementation SDSingleDog
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (@selector(girlfriend) == aSelector) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"___target = %@, sel = %@, methodSignature = %@", anInvocation.target, NSStringFromSelector(anInvocation.selector), anInvocation.methodSignature); } @ the end * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 13 14:49:32.360478+0800 KCObjcBuild[2748:93742] ___target = <SDSingleDog: 0x1058a8360>, sel = girlfriend, methodSignature = <NSMethodSignature: 0xb78f2e4cb57ba1c7>
Copy the code
methodSignatureForSelector:
Method returns as neededNSMethodSignature
Object, will be calledforwardInvocation:
Method for message forwarding,anInvocation
ParameterNSMethodSignature
Method signature, which was forwardedselector
, and method receiverstarget
. At this point, the program no longer crashes and can forward the message as needed. If the forwarded object also fails to respond to the message, it can use its ownforwardInvocation:
Method to continue forwarding.
@implementation SDSingleDog
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (@selector(girlfriend) == aSelector) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"___target = %@, sel = %@, methodSignature = %@", anInvocation.target, NSStringFromSelector(anInvocation.selector), anInvocation.methodSignature);
SEL aSelector = [anInvocation selector];
if (aSelector == @selector(girlfriend)) {
XJBoy *boy = [[XJBoy alloc]init];
anInvocation.target = boy;
[anInvocation invoke];
}
}
@end
@implementation XJBoy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (@selector(girlfriend) == aSelector) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"___target = %@, sel = %@, methodSignature = %@", anInvocation.target, NSStringFromSelector(anInvocation.selector), anInvocation.methodSignature);
SEL aSelector = [anInvocation selector];
if (aSelector == @selector(girlfriend)) {
XJPerson *person = [[XJPerson alloc]init];
anInvocation.target = person;
anInvocation.selector = @selector(loveEveryone);
[anInvocation invoke];
}
}
@end
@implementation XJPerson
- (void)loveEveryone
{
NSLog(@"%s, Everyone loves me, I love everyone", __func__); } @ the end * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 13 15:22:01.636226+0800 KCObjcBuild[3262:114582] ___target = <SDSingleDog: 0x10090df20>, sel = girlfriend, methodSignature = <NSMethodSignature: 0x9f2189f206557a37>
202107 -- 13 15:22:01.637765+0800 KCObjcBuild[3262:114582] ___target = <XJBoy: 0x10071a760>, sel = girlfriend, methodSignature = <NSMethodSignature: 0x9f2189f206557a37>
202107 -- 13 15:22:01.637832+0800 KCObjcBuild[3262:114582] -[XJPerson loveEveryone], Everyone loves me, I love everyone
Copy the code
SDSingleDog
Unable to respond togirlfriend
Message, forwards the message toXJBoy
.XJBoy
Can’t respond either. Continue forwarding toXJPerson
And willselector
Change toloveEveryone
And finally theXJPerson
The corresponding method is found and the message is forwarded successfully.
ForwardInvocation: The method is like the unrecognized invocation center, it can specify a new receiver for an unrecognized message, or translate it into another message, or just “eat” the message, so it doesn’t crash even if it’s not processed.
4: Summary and flowchart of message forwarding
4.1: Summary of message forwarding
- Quick forward: Yes
forwardingTargetForSelector:
Method returns an object that should be pointed to first for an unidentified message to receivenil
andself
), performs the lookup process of the object (method fast lookup -> method slow lookup, also recursively until the parent classnil
), if it is not found, it enters the slow forwarding process. - Slow forwarding: Yes
methodSignatureForSelector:
Methods andforwardInvocation:
Methods are implemented together ifmethodSignatureForSelector:
Method returnsnil
, the slow search process ends; If the correct method signature is returned,forwardInvocation:
Method can be forwarded, new message recipients can be specified, and changes can be madeselector
, and the specified new receiver can forward again.
4.2: Message forwarding flowchart
5: Disassembly explorationCoreFoundation
As previously analyzed, CoreFoundation is not fully open source and the source code for message forwarding is not visible. To explore the corresponding process, we use disassembly to explore it.
Select a CoreFoundation executable file, create a new arm64 architecture project, click a break point to break it, then use the LLDB image list command to output the path of all the image files, find the CoreFoundation corresponding path, open Finder, At the same time, hold command + shift + G, call out to folder, paste the path of the CoreFoundation into it and press enter to get it.
5.1: Hopper
exploreCoreFoundation
Open the CoreFoundation executable with Hopper and search for the _forwarding_prep_0 function. There is only one in the global file, and the ___forwarding___ function is called as shown in the crash message. Double-click to enter ___forwarding___.
Fast forwarding process
- if
forwardingTargetForSelector:
Method not implemented, jumploc_64a67
Process. - if
forwardingTargetForSelector:
Method returnsnil
orself
Jump,loc_64a67
Process.
Slow forwarding process
- if
methodSignatureForSelector:
Method not implemented, jumploc_64dd7
Process. - if
methodSignatureForSelector:
Method returnsnil
Jump,loc_64e3c
Process. - if
methodSignatureForSelector:
Method returns the method signature and continues the process.
loc_64dd7
Process: direct output error, and then jumploc_64e3c
Process.loc_64e3c
Process: calldoesNotRecognizeSelector:
Methods.
doesNotRecognizeSelector:
Method outputs an error message based on whether the message receiver is a class or an object, and then throws an exception.
- if
methodSignatureForSelector:
Method returns a method signature, based on which it is generatedNSInvocation
Class, and then callforwardInvocation:
Methods.
5.2: ida
exploreCoreFoundation
Open the CoreFoundation executable file with IDA, right-click on the Function Name menu bar on the left, select Quick Filter to search for the _CF_forwarding_prep_0 Function, and click F5 to convert it to pseudo-code mode to view. It is found that there is only one global Function. The ___forwarding___ function is called as shown in the crash message. Double-click to enter ___forwarding___.
Fast forwarding process
forwardingTargetForSelector:
Method, not implemented, returnnil
orself
Jump Indicates the slow forwarding process. Otherwise, fast forwarding is performed.
Slow forwarding process
methodSignatureForSelector:
Not implemented, error calldoesNotRecognizeSelector:
Method throws an exception.
methodSignatureForSelector:
Method returnsnil
, you jumpLABEL_50
calldoesNotRecognizeSelector:
Method throws an exception.- if
methodSignatureForSelector:
Method returns a method signature, based on which it is generatedNSInvocation
Class, and then callforwardInvocation:
Methods.
5.3: Summary of disassembly
- According to two disassembly tools for
CoreFoundation
And found that the message forwarding process was roughly the same as we guessed. - Disassembly is a great way to explore when we don’t know the underlying implementation and don’t have the relevant source code.
6:
- Method search is a complex and tedious process, there are fast search, slow search, Apple for the stability of the program, but also made a lot of efforts, such as dynamic method resolution, fast message forwarding, slow message forwarding, etc., can be said to be well-meaning. about
objc_msgSend
This is basically the end of the exploration.