preface

We examined cache_t in the last article. You’ve seen the structure of CACHE_T and the underlying process by which methods are invoked. In this article we will take a look at some of the Runtime API objc_msgSend.

RuntimeRuntime analysis

1. RuntimeAn overview of the

Compile-time, as the name implies, is compilation time, when the compiler translates your source code into code that the machine can recognize. (Of course, this is only in the sense that it might actually be translated into some intermediate state of speech.)

Runtime is, as the name implies, loaded into memory after the code has been run. When your code is stored on disk, it is “dead” until it is written to memory, and “alive” until it is written to memory. And unlike compile-time type checking (or static type checking), runtime type checking does not simply scan code, but does some operations and judgments in memory.

There are two versions of Runtime: a Legacy version (an earlier version); A Modern version (current version).

  • The programming interface for earlier versions: Objective-C 1.0
  • Current version of the programming interface: Objective-C 2.0
  • Earlier versions were used on Objective-C 1.0, 32-bit Mac OS X platforms
  • The current version is used for iPhone programs and 64-bit programs in Mac OS V10.5 and later.

Objective-c Runtime Programming Guide

2. RuntimeThree ways to call

    1. Objective-c method calls;
    1. Apis provided by NSObject;
    1. Runtime API objc_MSG_send method;

Runtime Initiator information diagram of the three invocation modes

Let’s look at the three ways Runtime calls can be made in code

Add the following code:

#import <Foundation/Foundation.h> #import <objc/message.h> @interface TPerson : NSObject - (void)sayNB; @end @implementation TPerson - (void)sayNB{ NSLog(@"666"); } @end int main(int argc, const char * argv[]) { @autoreleasepool { TPerson * tp = [TPerson alloc]; [tp sayNB]; [tp sayHello]; NSLog(@"Hello, World!" ); } return 0; }Copy the code

Generate.cpp files from Clang. Locate the main function in the.cpp file and look at its underlying code.

  • It turns out that in the underlying code, the code at the top gets interpreted after compilation.
  • Is the process of calling a method (sending a message), objc_msgSend(receiver of message, body of message (SEL+ body))

Can we also call the underlying message sending methods directly? So let’s try it out. Set Build Settings -> Enable Strict Checking of objc_msgSend Calls to NO.

TPerson * tp = [TPerson alloc];
[tp sayNB];
objc_msgSend(tp, sel_registerName("sayNB"));
Copy the code

Program running! Note We can call objc_msgSend() directly. Next, let’s try TPerson inheriting TTeacher.

@interface TTeacher : NSObject - (void)sayHello; @end @implementation TTeacher - (void)sayHello{ NSLog(@"666 %s",__func__); } @end @interface TPerson : TTeacher - (void)sayHello; - (void)sayNB; @end @implementation TPerson - (void)sayNB{ NSLog(@"666"); } @end int main(int argc, const char * argv[]) { @autoreleasepool { TPerson * tp = [TPerson alloc]; TTeacher * tt = [TTeacher alloc]; [tp sayNB]; // objc_msgSend(tp, @selector(sayNB)); [tp sayHello]; NSLog(@"Hello, World!" ); } return 0; }Copy the code

Run the code, generate the.cpp file, and see how the Runtime handles it at Runtime. Note: When generating the.cpp file, you need to comment out the objc_msgSend() code, otherwise the generation will fail ~

When we look at objc_msgSend(), we find an objc_msgSendSuper(). Does a subclass not have the ability to send messages directly to its parent class? So let’s verify that.

On the first page, let’s look at the objc_msgSendSuper() declaration.

foundobjc_msgSendSuper()Declare the required parametersstruct objc_super * _Nonnull superSEL _Nonnull op, and some corresponding parameters.SELWe know that,objc_superWhat is? We don’t know. Let’s take a lookobjc_superWhat it is. Global searchobjc_super

It can be seen thatobjc_superThere are two arguments:receiversuper_class. So let’s just call itobjc_msgSendSuper

struct objc_super tcd_objc_super; tcd_objc_super.receiver = tp; tcd_objc_super.super_class = TTeacher.class; objc_msgSendSuper(&tcd_objc_super, @selector(sayHello)); / / output: 666 21-06-27 21:26:56.619880+0800 001- Run experience [12241:1255935] 666 -[TTeacher sayHello] 2021-06-27 21:26:56.619961+0800 001- [TTeacher sayHello]Copy the code

objc_msgSendAnalysis of the

Let’s open the source code and search for the global objc_msgSend.

What are we gonna do with all these files? How do you know it’s assembly code? Then we have a wave of SAO operation quick positioning.

The assembly file suffix is.s, so we look for the.s file.

Next we look at the objC-msG-arm64. s file and navigate to ENTRY _objc_msgSend (go to objc_msgSend).

So let’s do this line by line.

ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame // P0 indicates the address of the message receiver. This determines whether the message receiver has CMP P0, // Check whether the Taggedpointer type is supported // If the Taggedpointer type is supported, LNilOrTagged is processed as LNilOrTagged Return LReturnZero b.q LReturnZero #endif LDR p13 if Taggedpointer is not supported. [x0] // p13 = isa [x0] P16 = class getfromisa_p16 p13, 1, x0 // p16 = class // receiver->class // calls imp or objc_msgSend_uncached CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached #if SUPPORT_TAGGED_POINTERS LNilOrTagged: b.eq LReturnZero // nil check GetTaggedClass b LGetIsaDone // SUPPORT_TAGGED_POINTERS #endifCopy the code

Here we assign ISA to p13 and pass it in as a parameter to GetClassFromIsa_p16. So let’s see what’s going on in GetClassFromIsa_p16, okay?

.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if ! needs_auth */ #if SUPPORT_INDEXED_ISA // Indexed isa mov p16, \src // optimistically set dst = src tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa // isa in p16 is indexed adrp x10, _objc_indexed_classes@PAGE add x10, x10, _objc_indexed_classes@PAGEOFF ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array 1: If \needs_auth == 0 // _cache_getImp takes an authed class already SRC. Else // 64-bit Packed ISA // $0, $1, #ISA_MASK $1 = P13 P16 ExtractISA P16, \ SRC, \ auth_address. endif #else // 32-bit RAW ISA mov p16, \ SRC #endifCopy the code

Let’s look at the implementation of CacheLookup.

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant MOV x15, x16 stash the original isa Assign to x15 LLookupStart\Function: // p1 = SEL, p16 = isa // => isa -> isa #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS LDR p10, [x16, #CACHE] // p10 = mask|buckets lsr p11, p10, #48 // p11 = mask and p10, p10, #0xffffffffffff // p10 = buckets and w12, w1, W11 // x12 = _cmd & mask #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 // x16(isa) translation 16 Cache address (_bucketsAndMaybeMask address) // Store _bucketsAndMaybeMask address in register P11. ldr p11, [x16, #CACHE] // p11 = mask|buckets #if CONFIG_USE_PREOPT_CACHES #if __has_feature(ptrauth_calls) tbnz p11, #0, LLookupPreopt\Function and p10, p11, #0x0000ffffffffffff // p10 = buckets #else and p10, p11, Bucket = bucket = bucket = bucket = bucket = bucket LLookupPreopt\Function TBNZ p11, #0, LLookupPreopt\Function #endif eor p12, p1, p1, LSR #7 and p12, p12, p11, LSR #48 = (_cmd ^ (_cmd >> 7)) &mask #else and p10, p11, #0x0000ffffffffffff // p10 = buckets and p12, p1, p11, LSR #48 // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 ldr  p11, [x16, #CACHE] // p11 = mask|buckets and p10, p11, #~0xf // p10 = buckets and p11, p11, #0xf // p11 = maskShift mov p12, #0xffff lsr p11, p12, p11 // p11 = mask = 0xffff >> p11 and p12, p1, p11 // x12 = _cmd & mask #else #error Unsupported cache mask storage for ARM64. #endifCopy the code

The definition of the CACHE

#define CACHE            (2 * __SIZEOF_POINTER__)  // => CACHE = 16
Copy the code

Through the objc_msgSend underlying source code analysis. We know that the call to objc_msgSend is handled differently depending on the schema. Objc_msgSend does the following things: 1. Isa translation = _bucketsAndMaybeMask = _bucketsAndMaybeMask = _bucketsAndMaybeMask Read buckets’ address, that is, the first address of the cache. Get the index of the hash.

Isa -> cache(_bucketsAndMaybeMask) -> buckets -> hash index

conclusion

First of all, we have seen the difference between Runtime and Runtime. Then we have seen the three ways that Runtime can be called. Finally, we have seen that objc_msgSend can be called differently according to different architectures. Isa -> cache(_bucketsAndMaybeMask) -> buckets -> hash index.

Tips: This article is a little busy, if there are omissions continue to supplement.