From our analysis in the previous article, we learned a little bit about the underlying flow of cache_t. We started analyzing the cache with an INSERT operation. When did we start calling inserts?
insert()
Closed loop flow of
- We are in
insert()
Method to view call stack information
- Is a kind of the stack
LIFO
The data structure of,index = 0
Is the last call from0
Start to view the call information in sequence and findlog_and_fill_cache
Method calledcache.insert
Methods; Let’s seelog_and_fill_cache
The implementation of the:
- Call stack display, yes
lookUpImpOrForward
Call thelog_and_fill_cache
: - To view
objc-cache.mm
File, which can be seen according to the commentsinsert()
The insertion timing of the topmost layer is throughobjc_msgSend
Triggered. Let’s take a lookobjc_msgSend
;
Runtime understanding of runtime
What is the runtime
To understand the message flow, you must first understand the Runtime
Compile time
- Compile timeAs the name suggestsWhile compiling. The compilertheThe source codeTranslated intoA code that the machine can recognize(Of course, this is only in a general sense, but in fact it may only be translated intoThe language of intermediate states). Compile time passSyntax analysis,Lexical analysisIsocompile-time type checking (Static type checking) to find in the code
errors
orwarning
Error message at compile time, etc. - Static checking does not run code in memory, but scans it as text. Some people are wrong when they say that memory is allocated at compile time.
The runtime
- Runtime is when code is loaded into memory for execution via DYLD; Runtime type checking is not the same as compile-time type checking (or static type checking) described earlier. Not simply scanning code, but doing operations and judgments in memory.
Runtime
There are two versions oneLegacy
Version (early version), oneModern
Version (current version)
Runtime comes in two versions:
Legacy
The version is an earlier versionObjective - 1.0 C
for32
bitMacOS X
In the systemModern
Indicates the current versionObjective - 2.0 C
foriPhone
andMacOS X v10.5
After the64
A system of
More information about OC can be found in the Apple documentation
- Objective-C Runtime Programming Guide
- Apple Official Documentation
There are three ways to invoke the Runtime
OC
Methods:[p sayNB]
;NSObject
Apis provided:isKindofClass... ;
objc
The lower level APIobjc_msg_send
Methods:class_getInstanceSize... ;
Runtime. the initiator information of the three invocation modes is as follows
#import <Foundation/Foundation.h> #import <objc/message.h> @interface XJPerson : NSObject - (void)sayNB; @end @implementation XJPerson - (void)sayNB{ NSLog(@"666"); } @end int main(int argc, const char * argv[]) { @autoreleasepool { XJPerson * tp = [XJPerson alloc]; [tp sayNB]; [tp sayHello]; NSLog(@"Hello, World!" ); } return 0; }Copy the code
throughClang
generate.cpp
File. in.cpp
Found in the filemain
Function to view its underlying code.
- It turns out that the compiled code at the top has an explanation in the underlying code.
- Is the process of calling a method (sending a message), objc_msgSend(receiver of message, body of message (SEL+ body))
So can we just call the underlying message sending method? So let’s try it out. Set Build Settings -> Enable Strict Checking of objc_msgSend Calls to NO.
XJPerson * tp = [XJPerson 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 XJPerson inherits XJTeacher.
@interface XJTeacher : NSObject - (void)sayHello; @end @implementation XJTeacher - (void)sayHello{ NSLog(@"666 %s",__func__); } @end @interface XJPerson : XJTeacher - (void)sayHello; - (void)sayNB; @end @implementation XJPerson - (void)sayNB{ NSLog(@"666"); } @end int main(int argc, const char * argv[]) { @autoreleasepool { XJPerson * tp = [XJPerson alloc]; XJTeacher * tt = [XJTeacher 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 ~
We’re checking.objc_msgSend()
When I found that there was aobjc_msgSendSuper()
. Does a subclass not have the ability to send messages directly to its parent class? So let’s verify that.First let’s take a lookobjc_msgSendSuper()
In the statement.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 xj_objc_super; xj_objc_super.receiver = tp; xj_objc_super.super_class = XJTeacher.class; objc_msgSendSuper(&xj_objc_super, @selector(sayHello)); 666 -[XJTeacher sayHello] 666 -[XJTeacher sayHello]Copy the code
objc_msgSend
Analysis of the
Due to the dynamic characteristic of the OC language, basically all the methods are handled objc_msgSend, such a high frequency call of the underlying method, from the efficiency level considering it is best to use close to machine language, assembly language, assembly language at the same time based on address is safer, so the system is also implemented in assembly; Let’s take a look at objc_msgSend at the assembly level in the LibobJC library
- in
objc
Source searchobjc_msgSend
- Close all files and view the suffix as
.s
Assembler file of
- So let’s see
objc-msg-arm64.s
File, located toENTRY _objc_msgSend
Go to objc_msgSend.
Start analyzing line by line_objc_msgSend
Assembly part source code
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
Assign ISA to p13 and pass it in as a parameter to GetClassFromIsa_p16. So what do we do in GetClassFromIsa_p16?
.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
Analyze 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 several things in arm64:
- through
isa
findclass
; - in
CacheLookup
Through theisa
Translation to findcache
The find_bucketsAndMaybeMask
The address. - read
buckets
Address, which is the first address of the cache. - To obtain
The hash
The subscriptindex
.
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.