IOS & OpenGL & OpenGL ES & Metal
The message lookup phase of The Runtime has been explored. If the conditions are not met, the message forwarding phase will be entered
preface
The message forwarding of Runtime is divided into three steps:
- Dynamic method parsing
- Object method parsing
- Class method resolution
- Fast forward
- Slowly forward
- The method signature
- forward
First, dynamic method analysis
Based on the previous article, we looked at lookUpImpOrForward, a method that is called resolveMethod_locked if none of this is finally met
1.resolveMethod_locked
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] resolveInstanceMethod(inst, sel, cls); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] resolveClassMethod(inst, sel, cls); if (! lookUpImpOrNil(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } } // chances are that calling the resolver have populated the cache // so attempt using it return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE); }Copy the code
- judge
-
If CLS is not a metaclass, it is currently an object method and resolveInstanceMethod is called
-
If CLS is a metaclass, it is a class method. Call resolveClassMethod. If not, the resolveInstanceMethod method is repeated
-
- Last call
lookUpImpOrForward
Method, returnimp
2. Object method parsingresolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); if (! lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; } 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 = lookUpImpOrNil(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', 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() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code
-
Error tolerance judgment if (! LookUpImpOrNil (CLS, resolve_sel, CLS ->ISA())) looks upimpornil (CLS, resolve_sel, CLS ->ISA())). (NSObject has this method by default.)
-
If implemented, send a message to the current CLS via objc_msgSend, which calls resolveInstanceMethod: the implemented method, where we have manually added an IMP to sel
-
We then check it again with lookUpImpOrNil to get the IMP we added
-
It returns to lookUpImpOrForward, loops through it, and returns IMP
2. Class method analysisresolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(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
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
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 = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
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() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
Copy the code
-
Same fault tolerance as above
-
If implemented, send a message to the current CLS metaclass -> via objc_msgSend, call resolveClassMethod, and add imp to SEL as above
-
Note: If there is no implementation, the object method resolveInstanceMethod will be passed again because there is a special case where the parent of the metaclass is the root metaclass, and the parent of the root metaclass is NSObject, so you need to pass this method again to see if there is an implementation in NSObject.
- In other words, all object methods and class methods that are not implemented will go in
NSObject
The inside of theresolveInstanceMethod
Methods, but that does not mean that all anti-crash methods should be written here! The reason: - Coupling degree is too high
- This method has been overridden in subclasses
- Some special methods are jumped directly and the experience is poor
- In other words, all object methods and class methods that are not implemented will go in
Code examples:
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"%s ",__func__); NSString *name = NSStringFromSelector(sel); If ([name isEqual:@"sayHello"]) {// If ([name isEqual:@"sayHello"]) { NewIMP = class_getMethodImplementation(self, @selector(sayHappy)); Method newMethod = class_getInstanceMethod(self, @selector(sayHappy)); const char *newType = method_getTypeEncoding(newMethod); return class_addMethod(self, sel, newIMP, newType); } return [super resolveInstanceMethod:sel]; }Copy the code
Second, fast forwarding
If we continue along the above path, we will find that the method has run out and the clue will be lost…
Inside lookUpImpOrForward is the log_and_fill_cache method, which contains a logMessageSend call. One of the steps in the sequence (the methods are not shown here, but go straight to the destination) is to print the log. Where logs are stored:
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid **()); 六四运动Copy the code
Through global exploration, it is found that the printed switch is like this code:
instrumentObjcMessageSends(BOOL flag)
Copy the code
So, let’s run the code to have a look, (just create a project, the source code can not run) preparation:
extern void instrumentObjcMessageSends(BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *objc = [[LGPerson alloc] init]; / / turn on the switch instrumentObjcMessageSends (true); [objc sayHello]; [objc sayHello]; / / off switch instrumentObjcMessageSends (false); } return 0; }Copy the code
Let’s run it. Let’s find it/tmp/msgSends
Take a look at the log files stored in this path:
As you can see from the log, the dynamic method was called after being parsedforwardingTargetForSelector
Methods. Let’s go to the official to see what this code means:
Basically, find an alternate receiver that returns an object that implements the method.
Code examples:
- (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s ",__func__); NSString *name = NSStringFromSelector(aSelector); Return [Jack alloc]; if ([name isEqual:@"sayHello"]) {return [Jack alloc]; } return [super forwardingTargetForSelector:aSelector]; }Copy the code
If there is no alternate receiver, or if the method is not implemented, slow forwarding will occur
3. Slow forwarding
If the forwardInvocation is not implemented, the forwardInvocation will be called. If the forwardInvocation is not implemented, the forwardInvocation will be called.
It’s circled in the official introduction. It needs to be rewrittenforwardInvocation:
This method, I have to rewrite at the same timemethodSignatureForSelector:
Methods, we continue to look at the official introduction:
Slow flow process is go first methodSignatureForSelector provides a method signature, and then walk forwardInvocation by NSInvocation message forwarding
Code examples:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"%s ",__func__); NSString *name = NSStringFromSelector(aSelector); If ([name isEqual:@"sayHello"]) {return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; -(void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@"%s ",__func__); SEL aSelector = [anInvocation selector]; if ([[Jack alloc] respondsToSelector:aSelector]){ [anInvocation invokeWithTarget:[Jack alloc]]; }else{ [super forwardInvocation:anInvocation]; }}Copy the code
Four, the pit
In one case,resolveInstanceMethod is called twice
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"%s ",__func__); return [super resolveInstanceMethod:sel]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s ",__func__); return [super forwardingTargetForSelector:aSelector]; } / / get the method signature - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {NSLog (@ "% s", __func__); NSString *name = NSStringFromSelector(aSelector); If ([name isEqual:@"sayHello"]) {// Method signature v- return value, @- pass object, :- pass sel return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; -(void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@"%s ",__func__); }Copy the code
Cause: In callmethodSignatureForSelector
Method, we pass a type, and the system will do the matching, go toclass_getInstanceMethod
In this method:
Method class_getInstanceMethod(Class cls, SEL sel) { if (! cls || ! sel) return nil; // This deliberately avoids +initialize because it historically did so. // This implementation is a bit weird because it's the only place that // wants a Method instead of an IMP. #warning fixme build and search caches // Search method lists, try method resolver, etc. lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER); #warning fixme build and search caches return _class_getMethod(cls, sel); }Copy the code
It goes back to lookUpImpOrForward, which ends up at resolveMethod_locked, and then again at resolveInstanceMethod.
If we pass the resolveInstanceMethod method twice, it is because after the signature, the system does something: it matches the signature type we passed in, and calls class_getInstanceMethod again.
Five, the summary
Premise: The entire lookup process does not find the method and enters the message forwarding process
-
Start with dynamic method parsing and add a method implementation (give SEL an IMP)
- Object method, called
+(BOOL)resolveInstanceMethod:(SEL)sel
methods - Class method, called
+(BOOL)resolveClassMethod:(SEL)sel
methods
- Object method, called
-
Fast forward, if dynamic method parsing does not find the corresponding processing method, will come here
- call
- (id)forwardingTargetForSelector:(SEL)aSelector
Method, return an alternate receiver
- call
-
Slow forwarding, if fast forwarding is not processed, it’s going to come here
- The first step is to call
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
Method gets method signature - The second step, call
-(void)forwardInvocation:(NSInvocation *)anInvocation
throughNSInvocation
To implement message forwarding
- The first step is to call