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.
Runtime
Runtime analysis
1. Runtime
An 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. Runtime
Three ways to call
-
- Objective-c method calls;
-
- Apis provided by NSObject;
-
- 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 super
和 SEL _Nonnull op
, and some corresponding parameters.SEL
We know that,objc_super
What is? We don’t know. Let’s take a lookobjc_super
What it is. Global searchobjc_super
。
It can be seen thatobjc_super
There are two arguments:receiver
和 super_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_msgSend
Analysis 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.