This is the 7th day of my participation in the August More Text Challenge
First, runtime runtime
Compile time and run time
Compile time
What is compiling? A compiler helps you translate the source code into code that the machine can recognize. Compile time checks code, lexical analysis, syntax analysis. This process of checking is often called static type checkingThe runtime
The code runs and is loaded into memory. Runtime error checking is not the same as compile-time error checking. It is not a simple code scan, but rather an in-memory operation and judgment
2. Runtime version
There are two versions of Runtime: Legacy and Modern.
- Programming interfaces for earlier versions:
Objective - 1.0 C
Earlier versions were used forObjective - 1.0 C
, 32 bitsMac OS X
The platform of - Programming interface of current version:
Objective - 2.0 C
, often seen in the source codeOBJC2
Current version used forObjective - 2.0 C
, iPhone apps andMac OS X v10.5
And 64-bit programs in later systems
3. How the Runtime is initiated
The Runtime can be initiated in three ways:
- through
OC method
.[obj method]
- through
NSObject
The method ofisKindofClass
performSelector
- through
Objc underlying
To provide theapi
.class_getInstanceSize
objc_msgSend
4. Call the underlying analysis method
Look at the code below to create an object and call its two methods
LRPerson *person = [LRPerson alloc];
[person saySomething];
[person sayHello:@"Hello"];
Copy the code
Use clang to compile code into C++ code
LRPerson *person = ((LRPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LRPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello:"), (NSString *)&__NSConstantStringImpl__var_folders_tc_3wwljjy118gc8b_00djjc3sh0000gn_T_main_4057b5_mi_2);
Copy the code
You can see that underneath, methods are called through the objc_msgSend function
So the essence of a method call is message sending: objc_msgSend(receiver of message, body of message (sel + argument))
5, objc_msgSend
Now that the method call is sending the message through objc_msgSend. Is it possible to call the method directly in the code through objc_msgSend?
Call before the first inBuild Setting
Lt.Enable Strict Checking of objc_msgSend Calls
Set toNO
To turn off the compiler check
#import <objc/message.h>
LRPerson *person = [LRPerson alloc];
[person saySomething];
objc_msgSend(person, @selector(saySomething));
[person sayHello:@"Hello"];
Copy the code
After trying to print the same results, you can call the method directly with objc_msgSend
Objc_msgSend low-level assembly analysis
1, find assembly source code
At the call method interrupt point go into assembly and findobjc_msgSend
Just follow in and you’ll find itobjc_msgSend
fromobjc
The underlying source
Look in the underlying source codeobjc_msgSend
Directly, seearm64
Real machine architecture, inobjc_msg_arm64.s
View the underlying source code, findENTRY
The entranceFirst, look at the macro definition used in the source code.
2. Source code analysis
ENTRY _objc_msgSend _objc_msgSend(id receiver, SEL _cmd)
UNWIND _objc_msgSend, NoFrame
// CMP: p0 is compared with 0. P0 is the first parameter.
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS // For 64-bit systems, the TaggedPointers type is supported
b.le LNilOrTagged // (MSB tagged pointer looks negative) <=0 jump LNilOrTagged
#else
b.eq LReturnZero // ==0 The message is empty jump LReturnZero
#endif
ldr p13, [x0] // p13 = isa; // P13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class GetClassFromIsa_p16(ISA, 1, receiver)
LGetIsaDone: // After obtaining isa, proceed with the following process
// calls IMP and objc_msgSend_uncached (NORMAL,_objc_msgSend,__objc_msgSend_uncached)
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass // p16 = class
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
// Clear the register value
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
/ / return
ret
END_ENTRY _objc_msgSend
Copy the code
The general process is as follows:
- judge
_objc_msgSend
The first parameterreceiver
Whether it is empty, and determine whether it is supportedTaggedPointers
type - If the support and
receiver <= 0
Go to theLNilOrTagged
judgereceiver
Whether it is empty. If it is empty, jumpLReturnZero
, not for empty passGetTaggedClass
getp16 = class
, and jump toLGetIsaDone
- If we do not support
receiver
If the value is empty, jumpLReturnZero
.receiver
If it is not empty, go down and pass firstreceiver
To obtainisa
And assign values to thep13
And then through theGetClassFromIsa_p16
The top16 = class
- They all end up there
LGetIsaDone
Go,CacheLookup
Cache lookup process
CacheLookup
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
...
mov x15, x16 // stash the original isa x15 = class
LLookupStart\Function:
// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS // arm64 emulator
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 / / arm64 real machine
// CACHE : (2 * __SIZEOF_POINTER__)
// [x16, #CACHE] = isa + 0x10 = CACHE address = _bucketsAndMaybeMask
ldr p11, [x16, #CACHE] // p11 = mask|buckets p11 = _bucketsAndMaybeMask
#if CONFIG_USE_PREOPT_CACHES / / arm64 real machine
#if __has_feature(ptrauth_calls) // A12 chip above iPhone X
tbnz p11, #0, LLookupPreopt\Function // if the TBNZ test bit is not 0, jump. And TBZ correspondence
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function // if _bucketsAndMaybeMask is bit 0! =0, jump to LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7 // eor (^) p12 = p1 ^(p1 >> 7)
// p12 = p12&(_bucketsAndMaybeMask >> 48) = buckets
and p12, p12, p11, LSR #48 // x12 = (_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.
#endif
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// go through the search
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket
cmp p9, p1 // if (sel ! = _cmd)
b.ne 3f // Scan more Go to Step 3
// } else {
2: CacheHit \Mode // HIT: Call or return IMP If a cache hit, imp is returned
/ /}
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss; _objc_MSgsend_uncached
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel ! = 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
...
.endmacro
Copy the code
The general process is as follows:
class
Memory translation findcache
.cache
There arebucket
andmask
- By xor and shift
bucket
The subscriptindex
- Take out the
bucket
In thesel
And what comes in_cmd
If they are equal, a cache hit is returnedimp
- Not equal
bucket
Pan to loop through the search - If you can’t find it, go
__objc_msgSend_uncached