Runtime understanding

Through this picture we can see that we usually callocMethods are essentially callsruntimeapi“Is to send messages. So our usualocMethod calls, how are they implemented at the bottom?

#import <Foundation/Foundation.h>
#import <objc/message.h>

@interface CXPerson : NSObject
- (void)sayHello;
@end

@implementation CXPerson
- (void)sayHello {
    NSLog(@"CXPerson - sayHello");
}
@end



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CXPerson *person = [CXPerson alloc];
        [person sayHello];
    }
    return 0;
}
Copy the code

Let’s create a project that defines a CXPerson class in the main.m file and calls the sayHello method.

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        CXPerson *person = ((CXPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CXPerson"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
    }
    return 0;
}
Copy the code

Then open the terminal, CD to the main.m file directory, and type clang-rewrite-objc main.m -o main. CPP to see a main. CPP file generated. Then find the implementation code for main. You can see that both alloc and sayHello calls are converted to objc_msgSend at the bottom.

You can see from the code that sending a message takes two parametersObjc_msgSend (Message receiver, message subject (SEL + argument)), message receiver and message subject. So can we call the underlying message sending method directly? So let’s try it out here.And you can see here that we call ourselves directlyobjc_msgSendThe method is also possible.

objc_msgSendThe source code to explore

So let’s take a look hereobjc_msgSendMethod to achieve the source code, global searchobjc_msgSendWe can see that in different architecturesobjc_msgSendCorresponding assembly code. Because what we use in real computers isarm64Architecture, let’s analyze it herearm64Framework under the assembly implementation.

  1. ENTRY _objc_msgSend(Go to objc_msgSend)
ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame // P0 indicates the address of the message receiver. CMP P0, #0 // nil check and tagged Pointer Check // If the receiver does not exist, it will go here. // (MSB tagged pointer looks) #if SUPPORT_TAGGED_POINTERS // if SUPPORT_TAGGED_POINTERS go to this point #else // Don't go here for SUPPORT_TAGGED_POINTERS. // Go here if message receiver exists. [x0] // p13 = isa (first address of message receiver) GetClassFromIsa_p16 p13, 1, X0 // p16 = class // The reason to get the class from the message receiver receviece is because the method resides in the cache, which is a property of the class structure. There is a cache_getIMP method in cache. Obtain imp through SEL. LGetIsaDone: // 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

This step determines whether the message recipient exists, and if so, assigns isa to p13, which is passed as a parameter to the GetClassFromIsa_p16 method.

  1. .macro GetClassFromIsa_p16 src, needs_auth, auth_address(Obtain class via ISA)
.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: #elif __LP64__ .if \needs_auth == 0 // _cache_getImp takes an authed class already mov p16, SRC. Else // 64-bit Packed ISA // and $0, $1, #ISA_MASK $1 = P13 P16 ExtractISA P16, \ SRC, \ auth_address. endif #else // 32-bit RAW ISA mov p16, \ SRC #endifCopy the code

This step is mainly to get the class through isa, since the cache isa property of the class structure. Chache_getIMP exists in the cache.

Global search can be seenExtractISAThe realization of, representsThe $1And on theISA_MASKAnd assigned to it$0.

  1. .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant X16 // stash the original isa // start objc_msgSend LLookupStart\Function: // p1 = SEL, p16 = 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 # CACHE] / / p11 = mask | buckets # if CONFIG_USE_PREOPT_CACHES / / global search CONFIG_USE_PREOPT_CACHES (# if defined (__arm64__) && TARGET_OS_IOS && ! TARGET_OS_SIMULATOR && ! TARGET_OS_MACCATALYST#define CONFIG_USE_PREOPT_CACHES 1) get CONFIG_USE_PREOPT_CACHES = 1, #if __has_feature(ptrauth_calls) TBNZ p11, #0, LLookupPreopt\Function and p10, p11, # 0x0000ffFFFFFFFFFF // p10 = buckets #else // P11 = buckets # p10 and p10, p11 // p15 = buckets // p15 = buckets // If it is not zero, jump to LLookupPreopt TBNZ p11, #0, LLookupPreopt\Function #endif // if it is zero, go here. X12 eor p12, P1, P1, LSR #7 and P12, P12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) &mask #else Buckets and upper 0x0000ffffFFFFFFFF assigned to P10 and P10, p11, //p1 (_cmd) & mask -> index -> p12 = index 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 // p11 cache -> p10 = buckets // P1 (_cmd) &mask = index -> PTRSHIFT = 3 #define PTRSHIFT 3 // (_cmd & mask) << (1+PTRSHIFT) -> (_cmd & (1,2,3) 4) = b[I] // bucket add p13, p10, p12 LSL #(1+PTRSHIFT) // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) // do { // * bucket-- p17, Imp (p17) sel(p9) imp(p17) sel(p9) imp(p17) sel(p9) ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket-- cmp p9, p1 // if (sel ! = _cmd) { b.ne 3f // scan more // } else { 2: CacheHit \Mode // hit: call or return imp // } 3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss; cmp p13, p10 // } while (bucket >= buckets) b.hs 1bCopy the code

Call the CacheLookup method and fill in the arguments NORMAL, _objc_msgSend, and __objc_msgSend_uncached.

  1. ldr p11, [x16, #CACHE]

Shift the ADDRESS of X16 to CACHE size.

  1. #define CACHE (2 * __SIZEOF_POINTER__)

A global search for #define CACHE yields a CACHE of two Pointers of 16 bytes.

  1. [x16, #CACHE] -> p11 -> cache_t

So p11 is equal to cache_t.

  1. CONFIG_USE_PREOPT_CACHES
#if defined(__arm64__) && TARGET_OS_IOS && ! TARGET_OS_SIMULATOR && ! TARGET_OS_MACCATALYST #define CONFIG_USE_PREOPT_CACHES 1Copy the code

A global search for CONFIG_USE_PREOPT_CACHES gives CONFIG_USE_PREOPT_CACHES equal to 1.

P11 = buckets = p10 and p10, p11, p11 = buckets = p10 If not zero, jump to LLookupPreopt TBNZ p11, #0, LLookupPreopt\FunctionCopy the code
  1. LLookupPreopt\Function

  2. CacheHit

.macro cachehit. if $0 == NORMAL // if $0 = NORMAL, TailCallCachedImp x17, x10, x1, x16 // authenticate andCopy the code
  1. TailCallCachedImp
//CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa .macro TailCallCachedImp // $0 = cached imp, $1 = address of cached IMP, $2 = SEL, $3 = isa $3 // jump to $0(imp), execute imp method br $0Copy the code

The final summary

objc_msgSend(recevier, _cmd) sel -> imp

  1. judgerecevierIf there is a
  2. throughrecevierfindisafindclass(p16)
  3. classMemory translation getscache(bucket mask)
  4. Bucket mask -> bucket
  5. Mask Mask -> mask
  6. Insert hash function (mask_t)(value & mask);
  7. Find index for the first time
  8. Bucket + index -> The number of buckets in the entire cache
  9. bucket{imp, sel}
  10. Sel is compared to our _cmd lookup, if equal -> cacheHit(cacheHit) -> imp ^ class(isa) = imp -> (br) call imp
  11. If the buckets are not equal — > pan again — > find the buckets and compare again
  12. An infinite loop traverses the search
  13. If we don’t find it, we’ll leave__objc_msgSend_uncached (Execute the third parameter in the CacheLookup method)

To be continued…