IOS Low-level exploration series
- IOS low-level exploration – alloc & init
- IOS underlayer exploration – Calloc and Isa
- IOS Low-level exploration – classes
- IOS Low-level exploration – cache_t
- IOS Low-level exploration – Methods
- IOS Low-level exploration – message lookup
- IOS Low-level exploration – Message forwarding
- IOS Low-level exploration – app loading
- IOS low-level exploration – class loading
- IOS Low-level exploration – classification loading
- IOS low-level exploration – class extension and associated objects
- IOS Low-level exploration – KVC
- IOS Basics – KVO
IOS leak check and fill series
- IOS leak fix – PerfromSelector
- IOS bug fix – Threads
- – RunLoop for iOS
- IOS – LLVM & Clang
First, dynamic method analysis process analysis
We analyzed dynamic method parsing in the previous chapter “Message Finding.” In order to better understand the specific process, we will go directly to source tracing.
Let’s start with the _class_resolveMethod method.
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if(! lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); }}}Copy the code
The general process is as follows:
- Determine if the parse is a metaclass
- If it is not a metaclass, then
_class_resolveInstanceMethod
Perform object method dynamic parsing - Call if it is a metaclass
_class_resolveClassMethod
Perform dynamic class method parsing - After the class method is dynamically parsed, query again
cls
In theimp
If not, an object method dynamic parsing is performed
1.1 Object method dynamic parsing
Let’s look at the dynamic resolution of object methods first. We’ll go straight to the _class_resolveInstanceMethod method:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, 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 = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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
The general process is as follows:
- Check if this is implemented
+(BOOL)resolveInstanceMethod:(SEL)sel
Class method that returns directly if not implemented (throughcls->ISA()
Get the metaclass, because class methods are object methods stored on the metaclass. - If currently implemented
+(BOOL)resolveInstanceMethod:(SEL)sel
Class method, passesobjc_msgSend
Call the class method manually - After the call is complete, query again
cls
In theimp
- if
imp
If it does, it logs the success of the dynamic resolution object method - if
imp
If not, the output is implemented+(BOOL)resolveInstanceMethod:(SEL)sel
And returnedYES
“, but did not find itimp
The log
1.2 Class method dynamic parsing
The _class_resolveClassMethod method is used to dynamically resolve the class method:
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_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 = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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
The general process is as follows:
- Assert whether it is a metaclass, if not, exit
- Check if this is implemented
+(BOOL)resolveClassMethod:(SEL)sel
Class method that returns directly if not implemented (throughcls-
Because of the currentcls
Metaclass, because class methods are object methods stored on metaclass.) - If currently implemented
+(BOOL)resolveClassMethod:(SEL)sel
Class method, passesobjc_msgSend
Call the class method manually, and note that unlike the dynamic object resolution method, this is requiredFind classes by metaclass and object, that is,_class_getNonMetaClass
- After the call is complete, query again
cls
In theimp
- if
imp
If it does, it logs the success of the dynamic resolution object method - if
imp
If not, the output is implemented+(BOOL)resolveClassMethod:(SEL)sel
And returnedYES
“, but did not find itimp
The log
One thing to note here is that if we try self instead of objc_getMetaClass(“LGPerson”) in the above example, this will cause the +(BOOL)resolveInstanceMethod (SEL) SEL method to be called, The problem actually happens at the class_getMethodImplementation method, which internally calls the _class_resolveMethod method, and our CLS passes self, So it will go again +(BOOL)resolveInstanceMethod:(SEL) SEL
1.3 specialNSObject
Object methods resolve dynamically
If CLS is a metaclass, that is to say, dynamic resolution of class methods is performed, the following source code is available:
_class_resolveClassMethod(cls, sel, inst); // It has been processed
if(! lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Object method resolution
_class_resolveInstanceMethod(cls, sel, inst);
}
Copy the code
_class_resolveClassMethod isa bitmap that can be used to parse an object’s method if the dynamic resolution fails.
We know from this flow chart that the metaclass ultimately inherits from the root metaclass, which in turn inherits from NSObject, so that means that class methods stored in the root metaclass are equivalent to object methods stored in NSObject. When lookUpImpOrNil is executed, the system recursively looks for a list of methods on the parent of the metaclass. Because the metaclass and root metaclass are automatically generated, we can’t write them directly. For NSObject, we can use categories to achieve uniform dynamic resolution of class methods, but only if the class itself does not implement the resolveClassMethod method:
This explains why _class_resolveClassMethod takes another step in the object method resolution process.
2. Quick process of message forwarding
What happens next in the message lookup process if we don’t have dynamic method resolution?
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
Copy the code
The lookUpImpOrForward source code returns _objc_msgForward_impcache when dynamic parsing fails. Objc-msg-arm64.s assembler source objC-msG-arm64.s assembler source objC-msG-arm64
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
We can see that __objc_msgForward_impcache jumps to __objc_msgForward, and we get no useful information from __objc_msgForward. Is that when the trail goes cold? We have a meeting in front of the process, if found imp, cache filling and log printing, we might as well find the printed log file to see if there will be the content we need.
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if(! cacheIt)return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (- 1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = - 1;
return true; }}// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : The '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
Copy the code
Here we can clearly see where the log file is stored and how it has been named:
Here’s another caveat:
ObjcMsgLogEnabled will only be logged if objcMsgLogEnabled is true. We will search directly for where this value appears:
Obviously, by calling the instrumentObjcMessageSends to achieve print on and off. Here’s a simple test:
Let’s run it and go to /private/ TMP:
Let’s open this file:
We saw the familiar resolveInstanceMethod, but after that there are two methods: that we don’t have explored before forwardingTargetForSelector and methodSignatureForSelector. Then there will be print of doesNotRecognizeSelector method, at this time Xcode console print as follows:
We can see that ___forwarding___ occurs in the CoreFoundation framework. We are still as usual, will be subject to the official documentation, check the forwardingTargetForSelector and methodSignatureForSelector.
First forwardingTargetForSelector:
ForwardingTargetForSelector official definition is returned to the IMP’s message directed to the first object was not found, the said person is in this way can realize the civet cats in prince, can’t find the IMP, isn’t it, I send this message to other objects to deal with is not just a matter of? Let’s go straight to code:
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
Copy the code
Here we return [LGTeacher alloc] directly, let’s run it:
We send “saySomething” to LGStudent, and LGTeacher responds to the message. About forwardingTargetForSelector, apple also gives some hints:
If an object implements (or inherits) this method, and returns a non-nil (and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously If you return self from this method, the code would just fall into an infinite loop. If an object implements or inherits this method and then returns a result that is not null (not self), the return value is treated as the new message recipient object and the message is forwarded to that object. If you return self in this method, then obviously an infinite loop will occur.
If you implement this method in a non-root class, Invoking Super’s if your class has nothing to return for the given selector then you should return the result of invoking Super’s implementation. If you implement this method in a non-base class that has nothing to return, then you need to return the implementation of the parent class. Is the return [super forwardingTargetForSelector: aSelector]; .
This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding. This method gives the object a chance to redirect unknown messages sent to it before the more expensive forwardInvocation: mechanical invocation. This is useful when you just want to redirect a message to another object and do it one order of magnitude faster than normal forwarding. This feature is useless in cases where the goal of the forwarding is to capture the NSInvocation or manipulate the parameters or return values during the forwarding.
From the above official document definition, we can clarify the thinking:
forwardingTargetForSelector
Is a fast message forwarding process that directly causes other objects to respond to unknown messages.forwardingTargetForSelector
Can’t returnself
Otherwise it will fall into an infinite loop because of returnself
Go back to the current instance object and go through the message lookup process, and obviously come backforwardingTargetForSelector
.forwardingTargetForSelector
If the message is forwarded to another object that can respond to the unknown message, the final return must be the same as the parameter and return value of the message to be searched. Otherwise, you need to go through another process.
Slow process of message forwarding
Above said if you want to eventually return must find the news and the content of the parameters and return values are not consistent, need to go to other processes, so what is the process, we then have a look at just another way to methodSignatureForSelector official documentation:
Official methodSignatureForSelector returns a NSMethodSignature method signature is the definition of object, the object contains the by a given selector identifies the description of the method.
This method is used in the implementation of protocols. This method is also used in situations where an NSInvocation object must be created, such as during message forwarding. If your object maintains a delegate or is capable of handling messages that it does not directly implement, you should override this method to return an appropriate method signature. This method is used for protocol implementation. This method is also used when the NSInvocation object must be created for message forwarding. If your object maintains a delegate or can handle messages that it does not implement directly, override this method to return the appropriate method signature.
We see at the end of the document that there is a method called forwardInvocation:
We go to the documentation for this method:
To respond to methods that your object does not itself recognize, you must override methodSignatureForSelector: in addition to forwardInvocation:. The mechanism for forwarding messages uses information obtained from methodSignatureForSelector: to create the NSInvocation object to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one. “To the response object itself can’t identify method, in addition to forwardInvocation: outside, still must be rewritten methodSignatureForSelector:. Forwarding message mechanism using from methodSignatureForSelector: access to the information to create the NSInvocation object is forwarding. Your override method must provide an appropriate method signature for a given selector, either by specifying a formula beforehand or by asking another object to provide a method signature.
Obviously, methodSignatureForSelector and forwardInvocation do not exist in isolation, need to appear together. Let’s go straight to the code:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[LGTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[LGTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
Copy the code
Then view the print:
As you can see, first came to the methodSignatureForSelector, then came to the forwardInvocation, saySomething message is looked up at last.
A few more points to note about the forwardInvocation:
forwardInvocation
The method has two tasks:- Lookup can respond
inInvocation
Object of the encoded message in. This object need not be the same for all messages. - use
anInvocation
The message is sent to the object.anInvocation
The results are saved, and the run-time system extracts the results and passes them to the original sender.
- Lookup can respond
forwardInvocation
Method implementations can do more than forward messages.forwardInvocation
It can also, for example, be used to combine code that responds to a variety of different messages, avoiding the hassle of having to write separate methods for each selector.forwardInvocation
Methods may also refer to several other objects in response to a given message, rather than forwarding it to just one object.NSObject
的forwardInvocation
Implementation: only callsDosNotRecognizeSelector: method that does not forward any messages. Therefore, if you choose not to implement
The forwardInvocation, where an unrecognized message to the object will raise an exception.
At this point, we have explored the slow process of message forwarding.
4. Message forwarding flow chart
From dynamic message parsing to fast forwarding process to slow forwarding process, we can summarize the following flow chart:
Five, the summary
We started with objc_msgSend to explore what happens after a message is sent, which is a great help in understanding the underlying iOS. Of course, due to the author’s level, the process of exploration may have some flaws. Here’s a quick summary:
- Dynamic method analysis is divided intoObject methods resolve dynamicallyandClass method dynamic resolution
- Object methods resolve dynamicallyA message sender implementation is required
+(BOOL)resolveInstanceMethod:(SEL)sel
methods - Class method dynamic resolutionA message sender implementation is required
+(BOOL)resolveClassMethod:(SEL)sel
methods
- Object methods resolve dynamicallyA message sender implementation is required
- Dynamic method resolution failure leads to message forwarding
- Message forwarding is divided into two processes: fast forwarding and slow forwarding
- The implementation of fast forwarding is
forwardingTargetForSelector
And let other objects that can respond to the lookup message do the work - The implementation of slow forwarding is
methodSignatureForSelector
和forwardInvocation
The combination provides finer – grained control by first returning method signatures toRuntime
And then letanInvocation
To send the message to the supplied object, and finally to theRuntime
The results are extracted and passed to the original message sender.
We’re in the seventh installment of iOS Basics, which starts with app loading, exploring cold and hot launches, and how DyLD works.