_objc_msgForward_impcache processing
The previous article analyzed the slow search process, if the recursive parent class still does not find IMP, imp = forward_IMP, because
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
Copy the code
Objc source global search _objc_msgForward_impcache
Focus on the compilation__objc_forward_handler
The implementation of the.
A global search for _objc_forward_handler(note that an underscore is removed) leads to c++ code:
Finally (message forwarding process) can not find the method to implement, error and print error message. Failed to find the method and finally reported an error process:_objc_msgForward_impcache
->_objc_forward_handler
->_objc_fatal
In the slow lookup process, if the IMP is not found and the dynamic method resolution has not been executed, the dynamic method resolution is executed once
Dynamic method resolution source code analysis
In lookUpImpOrForward, a dynamic method resolution is executed:
// First time: 0011&0010 = 0010
// second time: 0000&0010 = 0000
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//0010 ^ 0010 = 0000 = behavior
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
Copy the code
ResolveMethod_locked definition:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked(a);ASSERT(cls->isRealized());
runtimeLock.unlock(a);// programmer you are not stupid without this method - imp-nil
// Be friendly
// Give you a chance to save the earth -> IMP
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// Object method
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
/ / class methods
resolveClassMethod(inst, sel, cls);
// Methods in a metaclass are in the form of object methods
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls); }}// chances are that calling the resolver have populated the cache
// so attempt using it
// lookUpImpOrForwardTryCache->_lookUpImpTryCache->_lookUpImpTryCache->lookUpImpOrForward
// Slow search again
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
Copy the code
Object method resolution source code analysis
The object method resolves the resolveInstanceMethod implementation
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked(a);ASSERT(cls->isRealized());
// If CLS implements resolveInstanceMethod
SEL resolve_sel = @selector(resolveInstanceMethod:);
ResolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethod
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// The system will send a message to the resolveInstanceMethod in CLS
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, 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 = lookUpImpOrNilTryCache(inst, sel, cls); Omit some code}Copy the code
Object method resolution: ifcls
Implemented in theresolveInstanceMethod
, the system will giveresolveInstanceMethod
Send a message, if not implemented, the system will have aresolveInstanceMethod
Default implementation.
Object method resolution case debugging
1. Prepare debugging code:
#import <Foundation/Foundation.h>
@interface ABPerson : NSObject
-(void)doSomething;
@end
@implementation ABPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ABPerson *p = [ABPerson new];
[p doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
Copy the code
2. An error must be reported in the execution result:3. According to the object method resolution source code analysis, modificationABPerson
The implementation code
#import <objc/message.h>
@interface ABPerson : NSObject
-(void)doSomething;
@end
@implementation ABPerson
-(void)saySomething
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(self, @selector(saySomething));
Method method = class_getInstanceMethod(self, @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayImp, type);
}
return [super resolveInstanceMethod:sel];
}
Copy the code
4. No error is reported in the execution result, and the call doSomething is successfully converted to call saySomething.
Class method resolution source code analysis
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked(a);ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
ResolveClassMethod (resolveClassMethod, resolveClassMethod, resolveClassMethod)
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
// Failed to implement metaclass
if(! nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta); }}// The system sends a message to the resolveClassMethod in nonmeta
Methods in nonmeta(metaclass) are in the form of object methods
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(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 = lookUpImpOrNilTryCache(inst, sel, cls); Omit some code}Copy the code
Class method resolution: the system will giveNonmeta (yuan)
In theresolveClassMethod
Send a message, if not implemented, to the parent of the metaclass (the parent of the metaclass’s parent untilNSObject
So far) send messages. The system will also have aresolveClassMethod
Default implementation.
Class method Resolution case Debugging (1)
1. Prepare debugging code:
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface ABPeople : NSObject
+ (void)doSomething;
@end
@implementation ABPeople
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[ABPeople doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
Copy the code
2. An error must be reported in the execution result:
3. According to the object method resolution source code analysis, modify the IMPLEMENTATION code of ABPeople
#import <objc/message.h>
@interface ABPeople : NSObject
+ (void)doSomething;
@end
@implementation ABPeople
+ (void)saySomething
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveClassMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(objc_getMetaClass("ABPeople"), @selector(saySomething));
Method method = class_getInstanceMethod(objc_getMetaClass("ABPeople"), @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("ABPeople"), sel, sayImp, type);
}
return [super resolveClassMethod:sel];
}
@end
Copy the code
4. No error message is displayed in the execution result, and the call is successfully executeddoSomething
To callsaySomething
.
Class method resolution case Debugging (2)
We know that class methods exist as power methods in metaclasses, and the parent of a metaclass will find NSObject if you go all the way up, so NSObject class can also implement class method resolution.
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface ABPeople : NSObject
// Power method
- (void)doSomething;
/ / class methods
+ (void)doSomething;
@end
@implementation ABPeople
@end
@interface NSObject (Cate)
@end
@implementation NSObject (Cate)
-(void)saySomething
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(self, @selector(saySomething));
Method method = class_getInstanceMethod(self, @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayImp, type);
}
return NO;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[ABPeople doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
Copy the code
It can still run successfully. The same class resolution applies to the instance method
AOP is faceted oriented programming
If (sel == @selector(doSomething)), you can use a specific method, such as doSomething, to perform subsequent processing, such as uploading logs. This allows you to inject code dynamically without invading the original code. However, doing so also has corresponding performance costs, such as excessive if (sel == @selector(doSomething)) judgments, and truncates the subsequent message forwarding process. So it is not recommended to do AOP in the dynamic method resolution phase.
InstrumentObjcMessageSends write function call log
InstrumentObjcMessageSends source code analysis
InstrumentObjcMessageSends function definitions:
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
// If true, get some method trace
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if(objcMsgLogFD ! =- 1)
fsync (objcMsgLogFD);
//flag is assigned to objcMsgLogEnabled
objcMsgLogEnabled = enable;
}
Copy the code
ObjcMsgLogEnabled in logMessageSend
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = - 1;
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (- 1))
{
// Write log: "/ TMP/msgtimeline -%d", (int) getpid ()
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(a);write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock(a);// Tell caller to not cache the method
return false;
}
Copy the code
InstrumentObjcMessageSends use
InstrumentObjcMessageSends use is very simple, only need three steps:
1. Export functions for external use:
extern void instrumentObjcMessageSends(BOOL flag);
Copy the code
2. The need to track the function of up and down plus instrumentObjcMessageSends, such as:
ABPeople *p = [ABPeople new];
instrumentObjcMessageSends(YES);
[p doSomething];
instrumentObjcMessageSends(NO);
Copy the code
3. Path to the folder: / TMP /msgSends
conclusion
- Both object methods and class methods have an opportunity for dynamic method resolution that needs to be implemented in the class when the method is not found
resolveInstanceMethod
Object method implementation,resolveClassMethod
(class method implementation), through this method, subsequent processing can be carried out, if not implemented will enter the message forwarding process. - Class methods also exist in metaclasses in the form of instance methods, according to
isa
Inheritance chain, if you can’t find the method, you’ll always find itNSObjct
Through theNSObjct
Class implementation object dynamic method resolution, can be global listening, to carry outAOP
, but not recommended at this stageAOP
.