1, the preface
On an article from assembly to explore objc_msgSend left a __class_lookupMethodAndLoadCache3, followed by analysis of the method, search process will also come to the news, news search process can be divided into fast and slow, Quickly find has been found in objc_msgSend, can not find, would be the slow lookup, slow search process will be __class_lookupMethodAndLoadCache3 method, but I won’t direct analysis, the first to point out the bedding, want to see the source code analysis of the second step can be skipped.
2. Isa goes bitmap
We create Student and Person objects, Student -> Person -> NSObject(-> for inheritance), and then add an instance method and a class method to each object and to the NSObject class.
#pragma clang diagnostic push// Tell the compiler to ignore errors#pragma clang diagnostic ignored "-Wundeclared-selector"Student *student = [[Student alloc] init]; [studentSayHello]; [studentSayHello]; [studentSayHello]; // select student personSayNB from student personSayNB; NSObject [student nsobjectSayMaster]; // [student selector :@selector(saySomething)]; [studentSayObjc]; [studentSayObjc]; [Student personSayHappay]; NSObject [Student nsobjectSayEasy]; // [LGStudent performSelector:@selector(sayLove)]; [Student nsobjectSayEasy] [Student nsobjectSayEasy] [Student nsobjectSayEasy]#pragma clang diagnostic pop
Copy the code
I’ve already written up the results of the above method calls, but point 9, why a class calling NSObject’s object methods works, requires a pretty fancy diagram. Isa goes bitmap, which I’ll show you below. Since class methods are stored in the metaclass’s list of object methods, when a class calls a class method, It’s going to look in the object method list of the metaclass, if it doesn’t find it, it’s going to look in the root metaclass, and the parent of the root metaclass is NSObject, so it’s going to find NSObject, so the Student ends up finding nsobjectSayEasy in the object method list of NSObject.
3. Message search process
1. __class_lookupMethodAndLoadCache3 method analysis
When the assembler objc_msgSend does not find a method in the cache, it ends up going to this method for a slow lookup.
IMP _class_lookupMethodAndLoadCache3 (id obj, SEL SEL, Class CLS) {/ * CLS: if is the instance method that is, if it is a Class method that is metaclass SEL: method of obj: * / method callsreturn lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
Copy the code
2. LookUpImpOrForward Looks up the flow
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = nil; bool triedResolver = NO; runtimeLock.assertUnlocked(); // The value is NOif (cache) {
imp = cache_getImp(cls, sel);
if (imp) returnimp; } // prevent multiple threads from accessing two methods to return imp runtimelock.lock (); // Check illegal classes checkIsKnownClass(CLS);if(! CLS ->isRealized()) {// If the current class is not loaded, it needs to initialize the current class and load the properties and methods realizeClass(CLS); } // Do not initialize the current class without initializationif(initialize && ! cls->isInitialized()) { runtimeLock.unlock(); _class_initialize (_class_getNonMetaClass(cls, inst)); runtimeLock.lock(); // If sel == initialize, _class_initialize will send +initialize and //then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger thenIt won't happen. 2778172} Retry: runtimeLock. AssertLocked (); Imp = cache_getImp(CLS, sel);if (imp) goto done; Method meth = getMethodNoSuper_nolock(CLS, sel); // Method meth = getMethodNoSuper_nolock(CLS, sel);if(meth) {// find a cache log_and_fill_cache(CLS, meth->imp, sel, inst, CLS); imp = meth->imp; gotodone; Unsigned attempts = unreasonableClassCount();for(Class curClass = cls->superclass; curClass ! = nil; curClass = curClass->superclass) { // Haltif there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list."); } imp = cache_getImp(curClass, sel);if (imp) {
if(imp ! Log_and_fill_cache (CLS, IMP, sel, inst, curClass); gotodone;
}
else {
break; Method meth = getMethodNoSuper_nolock(curClass, sel);if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done; }}} // If the parent class and the parent class are not found, the dynamic method resolution is performedif(resolver && ! triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); TriedResolver = YES; triedResolver = YES; goto retry; }} No implementation found, and method resolver didn't help. Imp = (imp)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst);done:
runtimeLock.unlock();
return imp;
}
Copy the code
- Summary: The above overall process can be found, the method search flow will first go to the class to find, and then to the parent class and parent class to find, if not found, will be carried out
_class_resolveMethod
Dynamic method resolution (which will be analyzed in the next blog post) is called if none of the above helps_objc_msgForward_impcache
Method, what does this method do? In a minute.
3. _objc_msgForward_impcache exploration
Use a global search, step by step, to find the following assembly code in objC-msG-arm64. s assembly file, which will then call __objc_forward_handler.
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
_objc_forward_handler () ¶ This function is a C function, so we can search _objc_forward_handler globally to find the cause of the crash. Unrecognized selector sent to instance
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : The '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
Copy the code
4, summarize
- 1. When in
objc_msgSend
If no method is found in cache, it will arriveclass_lookupMethodAndLoadCache3
Conduct a slow lookup process. - In 2.
lookUpImpOrForward
It’s going to look for methods in this class firstgetMethodNoSuper_nolock
If this class is not found, it will recursively look for the parent class. - 3. If neither parent class nor parent class is found, dynamic method resolution is performed
_class_resolveMethod
This is the last chance Apple will give us. - 4. Dynamic methods we haven’t dealt with yet, and we’ll eventually get to
_objc_forward_handler
, and then crash to report an errorselector sent to instance
.