This is the 12th day of my participation in the August More Text Challenge. For details, see:August is more challenging
We analyzed the message lookup process earlier
- First, call
objc_msgSend
, fromcache
In the quick search, hit the execution of the corresponding IMP - Second, if it is not found in cache, it is called
lookUpImpOrForward
Do a slow search, find it, insert itcache
, so that the next execution can quickly find the call, and execute the correspondingimp
- Finally, when
lookUpImpOrForward
Function fromcache
andmethodTable
Can not find the correspondingMethod
, continue to execute down will comeresolveMethod_locked
Functions, or what we call dynamic method decisions
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
Copy the code
1. Source code analysis
1.1 resolveMethod_locked
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 (! lookUpImpOrNilTryCache(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } } // chances are that calling the resolver have populated the cache // so attempt using it return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);Copy the code
behavior & LOOKUP_RESOLVER behavior ^= LOOKUP_RESOLVER; These two steps ensure that resolveMethod_locked is executed only once
1.2 resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); if (! lookUpImpOrNilTryCache(cls, resolve_sel, CLS ->ISA(/*authenticated*/true))) {// Resolver not implemented. } 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. IMP IMP = lookUpImpOrNilTryCache(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
1.3 resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
// NSObject有兜底实现
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 = lookUpImpOrNilTryCache(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
ResolveMethod_locked sends resolveInstanceMethod: and resolveClassMethod: messages. In order to reduce crashes and improve user experience, Apple gives developers a chance to fix them. This process is called dynamic method resolution. Aop programming ideas are also present here, giving developers an aspect of the Objc_msg process to tap into what they want to handle, such as security handling, log collection, etc.
2 thinking
2.1 Why does resolveInstanceMethod call lookUpImpOrNilTryCache once and resolveMethod_locked call lookUpImpOrNilTryCache last? What is the effect of these two?
2.1.1 Analysis of the first TryCache process
Battle info –> The first tryCache will save the method I added dynamically
This time TryCache is calledlookUpImpOrForward
Function to find theMethodTable
. The ginsengbehavior
A value of4
, can not find imp words will not go dynamic resolution and message forwarding, directlyreturn nil
, branches are as follows:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
Copy the code
So what tryCache does in this case is find the method after the dynamic resolution is added, and call log_and_fill_cache to save it to the cache.
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
Copy the code
2.1.2 Analysis of the second TryCache process
Look at the comments first this time
// chances are that calling the resolver have populated the cache
// so attempt using it
Copy the code
Calling dynamic resolution might populate the cache and try to use it. The effect is simple and clear. In this call, the value of behavior is 1, and the IMP cannot find the methodTable, the dynamic resolution process will not be followed, but the message forwarding will be called
2.1.3 Why is it invoked twice?
Why not finally find the method, fill the cache and return, instead of first fill the cache and then try to find it from the cache, what’s the benefit of that? There is a multi-threaded guess: if thread A sends a message S into the dynamic resolution process, then thread B also sends a message S, at this time if the cache has been added to the IMP response message S, whether it will not continue to slow lookup, dynamic resolution and other subsequent process. Think of it this way: the more the dynamic resolution is added to the cache, the better. On the other hand, after we see the resolveClassMethod, we try to look it up in the cache, and we can’t find any calls to resolveInstanceMethod.
It can be seen that the thinking of apple developers in designing this process might be:
Since you are willing to add the IMP through dynamic method resolution, it is obvious that you want to use the IMP and probably use it frequently. In this case, I will cache it for you after the resolve method is called. In the future, if you want to use it, you can just pull it from the cache.
2.2 Why does the resolve class attempt to call instance resolve after the resolve class? Does instance resolve resolve the missing class method?
Let’s look at a classic picture of this
If we look for a class method along the inheritance chain and we end up with NSObject (the parent of rootMetaClass is NSObject), this leads to an interesting problem: our NSObject object method can respond to the SEL of the class method
Look at an instance
Add instanceMethod to NSObject
Send a class method message
ClassMethod is InstanceMethod, and InstanceMethod is InstanceMethod
* class_getClassMethod. Return the class method for the specified * class and selector. **********************************************************************/ Method class_getClassMethod(Class cls, SEL sel) { if (! cls || ! sel) return nil; return class_getInstanceMethod(cls->getMeta(), sel); }Copy the code
If you look for a classMethod, look for an InstanceMethod. If you look for a class method, look for an InstanceMethod. If you look for a class method, look for a class method. The IMP is not found in the cache, so resolveInstance is called again. Obviously, we can solve this problem by adding InstanceMethod to NSObject, and here we can add classMethod. After all, classMethod is InstanceMethod.