preface

Our last article looked at objc_msgSend looking up (the portal) in the cache. At the end of the article, we learned that when messages are not found in cache_t, the _objc_msgSend_uncached method is called, and it gets cached. Here we explore what the _objc_msgSend_uncached method actually does.

Explore the message sending process

_objc_msgSend_uncached

Ached _objc_msgSend_uncached in objC-msG-arm64.s

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
Copy the code

A method called _lookUpImpOrForward is found in assemble. look at _lookUpImpOrForwardWe don’t find the result we want, and look at lookUpImpOrForwardNow that we’ve found our method, we’ve moved from assembly to c++

Look at the lookUpImpOrForward procedure for source code

Overview of the entire approachWe see that the method is long and involves a lot of things, so let’s understand this methodInitialize assignment, where toForward_imp assignment, calling the __objc_msgForward method in assembly, IMP initializes nil, creating curClass. _objc_msgForward = _objc_msgForward = _objc_msgForward = _objc_msgForwardLet’s look again at __objc_forward_handler (When we search -- methods are not suitable, consider _ methods, because _ _ usually searches assembly and _ _ is usually C++). Look again at the objc_defaultForwardHandler method

The output text for finding a failure is familiar; it is an error message that cannot be found by our methods. There’s a + and a -, which means that both object methods and class methods go here. Let’s move on

This is a lock-free cache lookup, and if an IMP is found, done_nolock is called.This method is locking

  • Lock function, prevent multithreading resource snatch

    RuntimeLock is held during isRealize and isInitialized checks to prevent competing concurrent implementations

  • The reason for adding a runtime lock is that while searching for methods in the cache (read operations), classes can flush the cache (write operations) while reading or writing to a block of memory, which can cause problems.

    RuntimeLock is held during method search so that method lookup + cache fill remains atomicity relative to method addition. Otherwise, a category can be added, but it will be ignored indefinitely because the cache representing the category will be repopulated with the old values after it is refreshed.

Check to see if the class is loaded and in the cache

  • The method above is whether isRealized is implemented, and if not, implement it.

    RealizeClassMaybeSwiftAndLeaveLocked method calls to realizeClassWithoutSwift, inside has the following code CLS – > superclass = supercls; Tell compile time that the parent class of CLS is SuperCLs, addSubclass(superCLs, CLS); Tell compile time that this superCLs subclass is CLS. Therefore, in the process of isRealized, the parent classes of the recipients are identified one by one, and two-way binding is carried out.

  • The following method is to see if isInitialized is implemented, and if not, to implement it.

  • Let the lock hold
  • Assign the accepted CLS to curClass, which is already initialized.

Here is a for method (This is actually a for loop method, other methods have been deleted in the screenshot for easy viewing.)So let’s write a simple way to prove that this is a for loop

See if the if condition is satisfied and it breaks out of the loop.

From the current curClass (The recipient CLS is assigned to curClass aboveFind the SEL method in the method list and return. Let’s take a look at the implementation of getMethodNoSuper_nolock, with screenshots as the main method

  • CLS ->data()->methods() fetches bits from data() and methods() fetches the list of methods.
  • Search_method_list_inline is a lookup method that contains the famous binary lookup algorithm

Here we’ll look at how algorithm implementation, algorithm in findMethodInSortedMethodList, we overview the whole waySo in the red box is the idea of a binary algorithm so let’s see how this method represents the idea of a binary algorithm, so let’s say the method list contains 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, and the first time we look for 0x03, the second time we look for 0x06.

  • The bSAE we got above is the first address of the list, which is 0x01. (at a high level)
  • for (count = list->count; count ! = 0; Count >>= 1) {count >>= 1
  • probe = base + (count >> 1);If count is 8, let's move count to the right one bit., base is the first address bit, and probe is the offset of the first address of the list by 4 bits. Which is 0x05.
  • uintptr_t probeValue = (uintptr_t)probe->name; Obtained probe name
  • If (keyValue == probeValue) {if (keyValue == sel) { It’s really not equal here, so jump down, and let’s explain the way in
    • If probe is not the first digit, and the method name passed is equal to the value that probe retrieved by 1 bit forward, the offset is returned. (This is the classification of the same method, take precedence over the implementation of classification)
  • If (keyValue > probeValue) {if (keyValue > probeValue) {if (keyValue > probeValue) {if (keyValue > probeValue) {if (keyValue > probeValue) {if (keyValue > probeValue) {if (keyValue > probeValue) {

If the count is 4, if we move one more bit to the left, that’s 2, base is still the first one, probe is equal to the initial address is shifted by two places, and it reaches 0x02. If we repeat the loop, we find that the next bit is 0x02. So return the current probe. So let’s look for 0x06

If (keyValue > probeValue) if (keyValue > probeValue) if (keyValue > probeValue) if (keyValue > probeValue) if (keyValue > probeValue) if (keyValue > probeValue) count–; After that, base is equal to 0x60, and count becomes 7. It’s going to recirculate and it’s going to recirculate and it’s going to be 3, but it’s not going to be offset by 3, it’s not even, it’s going to be offset by 2, it’s going to be offset by 1, it’s going to be offset by 0x70, it’s not going to be offset, it’s going to recirculate and it’s going to be offset by 0x60.

Having said the above search procedure, proceed to the following method.If this method exists (Find this SEL),The done method calls log_and_fill_cache, and log_and_fill_cache calls cache_fill. Cache_fill calls cache->insert, Write the method to the cache_t cache. This is where methods of cache_t are not written to the cache. The following superclass finds methods that also execute the done method.

  • Set curClass equal to the parent of the current curClass, if curClass equals nil, come in, set imp equal to forward_IMP, and end

When the attempts made — operation is equal to 0, an error message is entered. Pause if there is a loop in the superclass chain.This method looks for sel in the list of methods of the parent class.If (slowPath ((slowpath = curClass->superclass) == nil)) {slowpath((slowpath = curClass->superclass) == nil)Let’s go to cache_getImp and take a look at the implementation and see the following code in assembly

STATIC_ENTRY _cache_getImp

	GetClassFromIsa_p16 p0
	CacheLookup GETIMP, _cache_getImp

LGetImpMiss:
	mov	p0, #0
	ret

	END_ENTRY _cache_getImp
Copy the code
  • After obtaining the ISA pointer,CacheLookup is called again with GETIMP, and as we saw in the previous article, if we can't find a method, we're going to call CheckMiss, which in GETIMP is LGetImpMiss. Return 0 x0. Printing also confirms this

Both of these methods are not satisfied, so we go to the next loop, if (slowPath ((curClass = curClass->superclass) == nil)) looks for the parent again until we find nil, which is the parent of NSObject, and then we go to judgment, Make IMP equal to forward_IMP, and forward_IMP equal to _objc_msgForward_impcache, break the loop.

Let’s start with dynamic method resolutionEnter this judgment, sel is unchanged, is still the method name passed in. The CLS is the same as the incoming receiver. The current CLS is not a metaclass, so the methods go to resolveMethod_lockedThis method only goes onceLet’s take a look at the content of the method

Explore the resolveMethod_locked method

This method will give you one last chance, and it will call resolveInstanceMethod. Let’s look at the resolveInstanceMethod method. Here’s an overview of the methodAmong them is a judgment:if (! LookUpImpOrNil (CLS, resolve_sel, CLS ->ISA())) {lookUpImpOrNil(CLS, resolve_sel, CLS ->ISA())) {lookUpImpOrForward is called again, resolveInstanceMethod:, and the CLS is the receiver.In other words, givenThe last chance is that this class or its parent simply implements resolveInstanceMethod:If it’s not implemented, it will also go log_and_fill_cache so let’s look at the log_and_fill_cache methodIt calls cache_fill, which calls cache-> INSERT, writing the method resolveInstanceMethod to cache_t and then refreshing cache_t. (The lookUpImpOrForward method is passed through many times, as many as six times, and it is important to clarify the message forwarding)

In this case, CLS is still the receiver, SEL is still resolveInstanceMethod:, IMP is resolveInstanceMethod, receiver is still the receiver.

If the resolveInstanceMethod method is not implemented, it willLookUpImpOrNil is called again, and the sel (not resolveInstanceMethod) looks up again Lockless cache lookup is performed.Imp still not found. However, log_and_fill_cache is still called to write unimplemented methods to cache_t to cache and refresh cache_t. But IMP is 0x0. If you go any further down it’s going to return nil, IMP doesn’t exist, it’s not implemented. And then we interrupt again,Sel is found in the same way as before, but the imp is changed to _objc_msgForward_impcache, that is, the imp is returned as _objc_msgForward_impcache.(Infer that when IMP returns nil, the method calls lookUpImpOrForward again, and the IMP is _objc_msgForward_impcache and returns.

Verify the correctness of the method flow

Write the following code:

@interface Person : NSObject
- (void)sayHello;
@end

@implementation Person
- (void)sayGood {
    NSLog(@"%s",__func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];
        [person sayHello];
    }
    return 0;
}
Copy the code

The code shows that Person does not implement sayHello, so the call is bound to return an error.The message indicates that the method was not found. ResolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethod: resolveInstanceMethodSo let’s run the codeSo our analysis is right.

Later we found sel lookUpImpOrForward method will have forwardingTargetForSelector and methodSignatureForSelector this our next article in the analysis.

The last

This article is the message sending search process, the search process involved inCache_t lookup, methodList lookup; Cache_t lookup for the parent class, methodList lookup; Know to find the location where NSObject's parent class is nil. So when we talked about methodList lookups, we talked a little bit more about binary lookups. If no message is found, the message is forwardedMessage forwarding we will cover very little in this article, and we will cover the message forwarding process in more detail in a later article. The article content is much, have wrong place, hope can correct. Finally, we make up a method search flow chart to end this article.