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
A,objc_msgSend
Assembly added
As we know, we use assembly to implement objc_msgSend for two reasons:
- Because C cannot write a function to preserve unknown parameters and jump to an arbitrary function pointer.
-
objc_msgSend
It has to be fast enough.
1.1 objc_msgSend
process
- ENTRY _objc_msgSend
- Determine and process the message receiver (id self, sel _cmd)
- TaggedPointer Judgment processing
-
GetClassFromIsa_p16
Isa pointer handler gets to class - CacheLookup finds the cache
-
Cache_t handles buckets as well as memory hashing
- I can’t find the next recursion
bucket
- Return when you find it
{imp, sel} = *bucket->imp
\ - Try again if you encounter an accident
- If you can’t find it, jump
junpMiss
- I can’t find the next recursion
-
__objc_msgSend_uncached
Cache not foundimp
STATIC ENTRY __objc_msgSend_uncached
-
Methodtable elookup MethodTableLookup
save parameters registers
-
self
As well as_cmd
To prepare -
_class_lookupMethodAndLoadCache3
call
Find the next process through assembly
When we’re exploring objc_msgSend, if we can’t find cache, we go to a place called objc_msgSend_uncached, and then we go to methodTable Cached, And then there will be a core __class_lookupMethodAndLoadCache3 lookup method. But we know we’re already in C/C++, so we can assemble it. We turn on the Always Show Disassembly option
Then we go inside objc_msgSend
Then we go inside _objc_msgSend_uncached
We will arrive at _class_lookupMethodAndLoadCache3, this is the method to find the real implementation.
Third, code analysis method search process
3.1 Object method test
- Object instance method – own
- Object instance method – does not have its own – finds the parent class
- Object instance method – no parent – no parent – find the parent – NSObject
- The instance method of the object – does not have it – does not have its parent – finds its parent – does not have NSObject – crashes
3.2 Class method testing
- Class method – own
- Class method – own no – find the parent class
- Class method – doesn’t have it – doesn’t have the parent – finds the parent class – NSObject
- Class method – doesn’t have it – doesn’t have the parent – finds the parent – doesn’t have NSObject – crashes
- Class method – doesn’t have it – doesn’t have a parent class – doesn’t have a parent class that finds the parent – NSObject doesn’t have it – but it does have object methods
Four, source code analysis method search process
We directly positioning to the _class_lookupMethodAndLoadCache3 source code:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}Copy the code
We then enter lookUpImpOrForward, and note that the cache is passed NO because it does not exist and therefore requires a method lookup.
4.1 lookUpImpOrForward
We then locate the source of lookUpImpOrForward:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)Copy the code
The arguments to this method tell us that lookUpImpOrForward should be a public method, with initialize and cache representing whether +initialize is avoided and whether to look up from the cache, respectively.
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}Copy the code
- if
cache
为YES
, then call directlycache_getImp
Come fromcls
From the cachesel
The correspondingIMP
If found, return.
if (! cls->isRealized()) { realizeClass(cls); }Copy the code
- Determine what you are currently looking for
cls
Whether you have completed the preparation work, if not, you need to perform the following classrealize
.
4.2 Searching from the current class
// Try this class's method lists. { Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; }}Copy the code
- The above method obviously looks up from the class’s list of methods
IMP
. The whole point of putting two curly braces here is to formLocal scope, so that the naming will not want to conflict. throughgetMethodNoSuper_nolock
To find theMethod
Is called when it is foundlog_and_fill_cache
Populate the cache and returnimp
.
2 getMethodNoSuper_nolock
static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) { runtimeLock.assertLocked(); assert(cls->isRealized()); // fixme nil cls? // fixme nil sel? for (auto mlists = cls->data()->methods.beginLists(), end = cls->data()->methods.endLists(); mlists ! = end; ++mlists) { method_t *m = search_method_list(*mlists, sel); if (m) return m; } return nil; } static method_t *search_method_list(const method_list_t *mlist, SEL sel) { int methodListIsFixedUp = mlist->isFixedUp(); int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) { return findMethodInSortedMethodList(sel, mlist); } else { // Linear search of unsorted method list for (auto& meth : *mlist) { if (meth.name == sel) return &meth; } } #if DEBUG // sanity-check negative results if (mlist->isFixedUp()) { for (auto& meth : *mlist) { if (meth.name == sel) { _objc_fatal("linear search worked when binary search did not"); } } } #endif return nil; }Copy the code
The getMethodNoSuper_nolock implementation is simple, simply iterating through the CLS data() and then calling search_method_list again to match sel on the iterated pointer to the method_list_t structure. The findMethodInSortedMethodList then we explore down here.
4.2.2 findMethodInSortedMethodList
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list) { assert(list); const method_t * const first = &list->first; const method_t *base = first; const method_t *probe; uintptr_t keyValue = (uintptr_t)key; uint32_t count; for (count = list->count; count ! = 0; count >>= 1) { probe = base + (count >> 1); uintptr_t probeValue = (uintptr_t)probe->name; if (keyValue == probeValue) { // `probe` is a match. // Rewind looking for the *first* occurrence of this value. // This is required for correct category overrides. while (probe > first && keyValue == (uintptr_t)probe[-1].name) { probe--; } return (method_t *)probe; } if (keyValue > probeValue) { base = probe + 1; count--; } } return nil; }Copy the code
FindMethodInSortedMethodList core logic is binary search, this algorithm is the premise of orderly collection.
4.3 Searching from the Parent Class
The source code is as follows:
// Try superclass caches and method lists. { unsigned attempts = unreasonableClassCount(); for (Class curClass = cls->superclass; curClass ! = nil; curClass = curClass->superclass) { // Halt if there is a cycle in the superclass chain. if (--attempts == 0) { _objc_fatal("Memory corruption in class list."); } // Superclass cache. imp = cache_getImp(curClass, sel); if (imp) { if (imp ! = (IMP)_objc_msgForward_impcache) { // Found the method in a superclass. Cache it in this class. log_and_fill_cache(cls, imp, sel, inst, curClass); goto done; } else { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; }}}Copy the code
// Superclass cache.
imp = cache_getImp(curClass, sel);Copy the code
- When you look up in the parent class, a bit different than when you look up in the current class is that you need to check the cache.
if (imp ! = (IMP)_objc_msgForward_impcache) { // Found the method in a superclass. Cache it in this class. log_and_fill_cache(cls, imp, sel, inst, curClass); goto done; } else { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; }Copy the code
- If you find it in the parent class
IMP
And determine whether it is the entry for message forwarding. If not, find itIMP
throughlog_and_fill_cache
Cached in the current class cache; If it is message forwarding, exit the loop.
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}Copy the code
- If the parent is not found in the cache, then look for the list of methods of the parent class, which is the same as the list of methods of the current class.
4.4 Method Analysis
// No implementation found. Try method resolver once. if (resolver && ! triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; goto retry; }Copy the code
If not found in either class or parent, Runtime gives us an opportunity to do dynamic method resolution.
/*********************************************************************** * _class_resolveMethod * Call +resolveClassMethod or +resolveInstanceMethod. * Returns nothing; any result would be potentially out-of-date already. * Does not check if the method already exists. **********************************************************************/ 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
Let’s analyze _class_resolveMethod:
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
- Call to determine whether the current class is a metaclass, if not
_class_resolveInstanceMethod
. - If it is a metaclass, you are looking for a class method
_class_resolveClassMethod
.
4.4.1 _class_resolveInstanceMethod
First we examine the dynamic object parsing 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() ? '+' : '-',
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));
}
}
}
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}Copy the code
Here’s another caveat:
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);Copy the code
Send a SEL_resolveInstanceMethod message to the current CLS. If YES is returned, the current class has implemented dynamic method resolution.
The code above shows that dynamic method resolution returns to lookUpImpOrForward at the end. Cache: YES; resolver: NO
Cache the result (good or bad) so the resolver doesn’t fire next time.
Caching the results of lookups so the parser won’t fire next time, essentially
Break the recursive.
4.4.2 _class_resolveClassMethod
Let’s move on to the dynamic parsing class methods:
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() ? '+' : '-', 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
One caveat: The CLS passed in must be metaclass, because the class methods are in the metaclass’s cache or list of methods.
Bool resolved = MSG (CLS, SEL_resolveInstanceMethod, sel); Bool resolved = MSG (_class_getNonMetaClass(CLS, INST), SEL_resolveClassMethod, sel);Copy the code
The first argument to the MSG method is the result of _class_getNonMetaClass(CLS, INST). Let’s go inside _class_getNonMetaClass:
Class _class_getNonMetaClass(Class cls, id obj)
{
mutex_locker_t lock(runtimeLock);
cls = getNonMetaClass(cls, obj);
assert(cls->isRealized());
return cls;
}Copy the code
Next we go to getNonMetaClass. The purpose of this method is to get the class from the metaclass. We remove some of the interference:
static Class getNonMetaClass(Class metacls, id inst) { static int total, named, secondary, sharedcache; realizeClass(metacls); total++; // If it is not already metaclass, return if (! metacls->isMetaClass()) return metacls; // metaclass really is a metaclass // Where inst == inst-> isa () == metacls is possible if (metacls-> isa () == metacls) {Class CLS = metacls->superclass; assert(cls->isRealized()); assert(! cls->isMetaClass()); assert(cls->ISA() == metacls); if (cls->ISA() == metacls) return cls; } // If (inst) {Class CLS = (Class)inst; realizeClass(cls); // CLS may be a subclass, where the class object is obtained by instance, and then a while loop is passed to determine whether the isa of the class object is metaclass. // CLS may be a subclass - find the real class for metacls while (CLS && CLS ->ISA()! = metacls) { cls = cls->superclass; realizeClass(cls); If (CLS) {assert(! cls->isMetaClass()); assert(cls->ISA() == metacls); return cls; } #if DEBUG _objc_fatal("cls is not an instance of metacls"); #else // release build: Class CLS = getClass(metacls->mangledName()); if (cls->ISA() == metacls) { named++; if (PrintInitializing) { _objc_inform("INITIALIZE: %d/%d (%g%%) "successful by name metaclass lookups", named, total, named*100.0/total); } realizeClass(cls); return cls; {Class CLS = (Class)NXMapGet(nonMetaClasses(), metacls); if (cls) { secondary++; if (PrintInitializing) { _objc_inform("INITIALIZE: %d (%g%%) ""successful secondary metaclass lookups", secondary, total, secondary*100.0/total); } assert(cls->ISA() == metacls); realizeClass(cls); return cls; }} try any duplicates in the dyld shared cache. {Class CLS = nil; int count; Class *classes = copyPreoptimizedClasses(metacls->mangledName(),&count); if (classes) { for (int i = 0; i < count; i++) { if (classes[i]->ISA() == metacls) { cls = classes[i]; break; } } free(classes); } if (cls) { sharedcache++; if (PrintInitializing) { _objc_inform("INITIALIZE: %d/%d (%g%%) "successful sharedcache metaclass lookups", sharedCache, total, sharedCache *100.0/total); } realizeClass(cls); return cls; } } _objc_fatal("no class for metaclass %p", (void*)metacls); }Copy the code
4.5 Message Forwarding
// 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
If dynamic message parsing still fails, then the final step of message lookup is message forwarding.
An IMP of type _objc_msgForward_impcache is returned and populated with cache_T in the CLS. At this point, our message lookup process ends.
Five, the summary
- Method lookup, or message lookup, starts with
_class_lookupMethodAndLoadCache3
. -
_class_lookupMethodAndLoadCache3
The core implementation oflookUpImpOrForward
. - from
_class_lookupMethodAndLoadCache3
If you go in, you ignore the cache and just look it up from the list of methods. - The lookup ensures that the class has completed things like properties, methods, protocols, and so on
attach
. - First from the current class method list search, find the return, find not to the parent class.
- If it finds a return, if it doesn’t find a method list, if it finds a return, it doesn’t find a dynamic method resolution.
- Object method dynamic resolution and class method dynamic resolution are performed according to whether the current is a class or metaclass.
- If the parsing succeeds, the message is returned. If the parsing fails, the message forwarding process is started.
We explored the basics of message lookup together today, and in the next chapter we will explore the process of method forwarding further along today’s path. Stay tuned ~