This article explores the nature of the method, but before doing so, let’s take a quick look at the Runtime.
The Runtime profile
Runtime is called runtime and is distinguished from compile time
-
Compilation is the process of translating source code into code that can be recognized by the machine. It mainly carries out the most basic check on the language, that is, lexical analysis, grammar analysis and so on. It is static type check.
-
Runtime is the process by which code runs, is loaded into memory, and if something goes wrong, the program crashes. Static type checking.
There are three ways in which the Runtime interacts
Objective-C Code
Direct call
For example, call the method [person eat] directly, #selector(), etc.
Framework&Serivce
Methods such as isKindeofClass and isMemberOfClass.
RuntimeAPI
Low-level methods like class_getInstanceSize.
Explore the nature of the method
Clang compiler
In the article exploring the underlying principle of ISA, through the source code of CLang compilation, we understand the essence of OC object, also we call methods in main, check the clang compilation main.cpp file.
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
[p eat];
[p say];
}
return 0;
}
Copy the code
The underlying implementation of clang after compilation
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
((void(*) (id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("eat"));
((void(*) (id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("say"));
}
return 0;
}
Copy the code
As you can see from the clang compiled code, the essence of the method is objc_msgSend message sending.
Call the method with objc_msgSend
#import
Select * from target –> Build Setting –> search MSG –> enable strict checking of obc_msgSend calls Otherwise, the objc_msgSend parameter will fail.
- The method call
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
[p eat];
objc_msgSend(p,sel_registerName("eat"));
}
return 0;
}
Copy the code
- Call the parent class method
Define a Person class
@interface Person : NSObject{
NSString *hobby;
}
@property (nonatomic.copy) NSString *name;
- (void)eat;
- (void)say;
+ (void)run;
@end
Copy the code
Define a Teacher class that integrates with the Person class
@interface Teacher : Person- (void)teach;
@end
Copy the code
The main function
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
Teacher *t = [Teacher alloc];
[p say];
struct objc_super lsuper;
lsuper.receiver = t;
lsuper.super_class = [Teacher class];
objc_msgSendSuper(&lsuper, sel_registerName("say"));
}
return 0;
}
Copy the code
- Objc_msgSendSuper source
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0.2.0.9.0.1.0.2.0);
#endif
Copy the code
- Objc_super source
- Print the result
conclusion
Nature of method
isObjc_msgSend Sends messages
- OC
A method is called
Equivalent to runtimeobjc_msgSend
andobjc_msgSendSuper
Message sending.
Explore objc_msgSend
-
In objC4-781 source code, search objc_msgSend. We use the arm64 architecture for daily development, so find objc_msgSend source code in the arm64.s file.
-
Objc_msgSend is implemented in assembly for two main reasons:
• Assembly is easier to machine recognize • Unknown parameters and unknown types are less handy for C and C++ than assembly
Objc_msgSend Assembler source code
//---- message sending -- assembler entry --objc_msgSend is mainly to get the recipient's ISA information
ENTRY _objc_msgSend
/ / -- no window
UNWIND _objc_msgSend, NoFrame
//---- p0 and null comparison to determine whether the receiver exists, where p0 is the first parameter of objc_msgSend - message receiver
cmp p0, #0 // nil check and tagged pointer check
//---- le is less than -- supports taggedPointer (small object type) processes
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//---- If p0 is 0, null is returned
b.eq LReturnZero
#endif
//---- p0 is the process that must exist at receiver
//---- fetch ISA according to the object, i.e. fetch ISA from the address pointed to by register x0 and store it in register p13
ldr p13, [x0] // p13 = isa
P16 = isa (p13) & ISA_MASK (p16 = isa (p13) & ISA_MASK
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//---- if you have isa, go to CacheLookup, the CacheLookup process, also known as sel-imp quick lookup process
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
//---- equals null, return null
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60#,4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52#,8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
Copy the code
_objc_msgSend Flow chart of an entry function
CacheLookup CacheLookup assembly source code
//!!!!!!!!! Key !!!!!!!!!!!!
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LLookupRecover$1 which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
LLookupStart$1:
//---- p1 = SEL, p16 = isa -- #define CACHE (2 * __SIZEOF_POINTER__), __SIZEOF_POINTER__
/ / - p11 = mask | buckets - from x16 16 bytes (isa) in translation, the cache is just remove the cache in the p11 register - isa distance 16 bytes: Isa (8 bytes) -superClass (8 bytes) - Cache (mask 16 bits lower than buckets 48 bits lower)
ldr p11, [x16, #CACHE]
//---- 64-bit true machine
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// p11(cache) &0x0000FFFFFFFFFFFF, 16 bits of mask are erased, and buckets are stored in p10 register
and p10, p11, #0x0000ffffffffffff // p10 = buckets
P11 (cache), p11(cache), p11(cache), p11(cache) The index of sel-IMP is obtained and stored in P12 (sel & mask is used for cache insert).
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
//-- non-64-bit true machine
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
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
P12 is the subscript p10 is the first address of buckets array. The subscript * 1<<4(that is, 16) is used to obtain the offset of the actual memory
// LSL #(1+PTRSHIFT); // LSL #(1+PTRSHIFT)
add p12, p10, p12, LSL #(1+PTRSHIFT)
// buckets = buckets + ((_cmd & mask) << (1+PTRSHIFT)
// select sel from p17 (select SEL from P9)
ldp p17, p9, [x12] // {imp, sel} = *bucket
//-- compare sel with p1 (CMD)
1: cmp p9, p1 // if (bucket->sel ! = _cmd)
//-- If not, that is, not found, please jump to 2f
b.ne 2f // scan more
// if it is equal, a cacheHit is hit, and imp is returned
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
// if not, go to __objc_msgSend_uncached because it is normal
CheckMiss $0 // miss if bucket->sel == 0
Check whether p12 (bucket) equals p10 (buckets). If so, go to step 3
cmp p12, p10 // wrap if bucket == buckets
//-- Locate the last element (the first bucket)
b.eq 3f
/ / - from x12 (p12 buckets first address) - actual need translation memory size BUCKET_SIZE, get the second element of the bucket, imp - sel in p17 - p9 respectively, namely, look ahead
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
//-- jump to step 1 and continue to compare sel and CMD
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//-- manually set to the last element
If p11 (mask) is moved 44 bits to the right, it is the same as if mask is moved 4 bits to the left. P11 (mask) is directly located to the last element of buckets
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
//-- find the cache again ()
P17-p9 p17-SEL p17-P9 p17-SEL p17-P9
ldp p17, p9, [x12] // {imp, sel} = *bucket
//-- compare sel with p1 (CMD)
1: cmp p9, p1 // if (bucket->sel ! = _cmd)
// if not, go to the second step
b.ne 2f // scan more
//-- if the match is hit, return IMP directly
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
// if not found, then CheckMiss
CheckMiss $0 // miss if bucket->sel == 0
P12 (bucket) = p10 (buckets); p10 (buckets) = p10 (buckets)
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f // If equals, go to step 3
/ / - from x12 (p12 buckets first address) - actual need translation memory size BUCKET_SIZE, get the second element of the bucket, imp - sel in p17 - p9 respectively, namely, look ahead
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
//-- jump to step 1 and continue to compare sel and CMD
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
// Jump to JumpMiss because it is normal and to __objc_msgSend_uncached
JumpMiss $0
.endmacro
Copy the code
CacheHit source code implementation
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
cbz p0, 9f // don't ptrauth a nil imp
AuthAndResignAsIMP x0, x12, x1, x16 // authenticate imp and re-sign as IMP
9: ret // return IMP
.elseif $0 == LOOKUP
// No nil check for ptrauth: the caller would crash anyway when they
// jump to a nil IMP. We don't care if that jump also fails ptrauth.
AuthAndResignAsIMP x17, x12, x1, x16 // authenticate imp and re-sign as IMP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
Copy the code
CheckMiss source code implementation
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
//-- if GETIMP, jump to LGetImpMiss
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
//--- 如果为NORMAL ,则跳转至 __objc_msgSend_uncached
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
//-- If LOOKUP, skip to __objc_msgLookup_uncached
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
Copy the code