Compile time at runtime

Compile time

Compile-time, as the name implies, is compilation time, when the compiler translates your source code into code that the machine can recognize. It’s really just a language translated into some intermediate state.

At compile time, it is simply a matter of doing translation work, such as checking code specifications, parsing and so on.

The runtime

Runtime is a dynamic process in which code runs and is stored in memory. Unlike compile-time type checking, runtime type checking does not simply scan the code, but does actual operations in memory.

The RunTime of the OC is what we call the RunTime.

There are three ways in which the Runtime interacts

  • Call objective-C Code directly

Call methods like [self say], #selector(), and so on.

  • Framework&Serivce

NSSelectorFromString, isKindOfClass, isMenberOfClass, etc.

  • RuntimeAPI

Low-level methods such as sel_registerName, class_getInstanceSize, etc.

Clang compiles OC source code

Environment to prepare

OC source code

@interface Person : NSObject

- (void) running;
- (void) swimming;

@end

@implementation Person

- (void) running
{
    NSLog(@"running");
}

- (void) swimming
{
    NSLog(@"swimming");
}

@end

@interface Student : Person

@end

@implementation Student

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];
        [person running];
        objc_msgSend(person, sel_registerName("running"));
        
        Student *student = [Student alloc];
        [student swimming];
        
        struct objc_super yjSuper;
        yjSuper.receiver = student;
        yjSuper.super_class = objc_getClass("Person");
        objc_msgSendSuper(&yjSuper, sel_registerName("swimming"));
    }
    return 0;
}
Copy the code

Clang conversion main. CPP

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

Method call decomposition

Call the running method with objc_msgSend

objc_msgSend(person, sel_registerName("running"));

Call the swimming method of the parent class with objc_msgSendSuper

objc_msgSendSuper(&yjSuper, sel_registerName("swimming"));

Struct objc_super * struct msgsendsuper * struct objc_super * struct msgsendsuper * struct msgsendsuper * struct msgsendsuper * struct msgsendsuper * struct msgsendsuper *

struct objc_super { /// Specifies an instance of a class. __unsafe_unretained _Nonnull id receiver; /// Specifies the particular superclass of the instance to message. #if ! defined(__cplusplus) && ! __OBJC2__ /* For compatibility with old objc-runtime.h header */ __unsafe_unretained _Nonnull Class class; #else __unsafe_unretained _Nonnull Class super_class; #endif /* super_class is the first class to search */ };Copy the code

The two parameters we use are receiver and super_class, the message receiver is our object, the parent is the Person object, and since we’re declaring a structure, The argument needs to be a pointer so we pass in the ampersand structure address as the argument.

Main.cpp analyzes the method call process

  • CPP file analysis shows that both alloc and RUNNING are called through objc_msgSend.
  • Objc_getClass () is the Runtime method that gets the Person class object.
  • Sel_registerName () is also a Runtime method used to get methods, corresponding to @selector () of OC, NSSelectorFromString().
  • We found that both the instance method and the class method are called through the objc_msgSend method, except that the message receiver of parameter 1 is the class object when the class method is called, the instance object when the instance method is called, and parameter 2 is the method we want to call.
  • Objc_msgSend does method lookups in the cache and in the method list based on parameter one.
  • Finding a method in the cache is the fastest, so we call it method fast lookup.

Objc_msgSend

introduce

Objc4 source code search found that objc_msgSend is implemented using assembly, the main characteristics of assembly are:

  • Fast, assembly is easier to machine recognition.
  • The dynamic nature of method arguments. When assembly calls a function, the parameters passed in are uncertain, so when messages are sent, a direct call to a function can send all messages.

Message lookup mechanism

Quick lookup: cache lookup (cache lookup).

Slow lookup: lookup (methodList) in methodList, and message forwarding

Objc_msgSend Quick lookup analysis

Objc_msgSend call

objc_msgSend(person, sel_registerName("running"));

Two parameters are passed, the message receiver and the SEL of the message.

Objc_msgSend Assembler source code

ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame // p0 is the first argument we pass in: Message receiver // CMP is a comparison method that compares whether p0 is nil. If nil means there is no message receiver, Return CMP p0, // nil check and tagged pointer check #if SUPPORT_TAGGED_POINTERS // TagPointer type // p13 returns the first address of the message receiver, Isa LDR p13, [x0] // p13 = isa // GetClassFromIsa_p16 x0 // p16 = class LGetIsaDone: Cached imp CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached // get cached CACHED IMP CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncachedCopy the code

The flow chart

CacheLookup source

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 MaskAndBuckets 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, #0x0000fffffffffffe // p10 = buckets tbnz p11, #0, LLookupPreopt\Function #endif eor p12, p1, p1, LSR #7 and p12, p12, P11, LSR #48 = (_cmd ^ (_cmd >> 7)) &mask #else A total of eight 64 - byte high 16 bytes stored the mask value lower 48 bytes to store the information / / buckets so here will mask | buckets & 0 x0000ffffffffffff for lower 48 bytes of information, P10 = buckets and p10, p11, # 0x0000FFFFFFFFFF Catch_hash = _cmd &mask = _cmd &mask And assign to p12 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 // p12 is _cmd and mask is the index. // (1+PTRSHIFT) == 4 // (_cmd & mask) << (1+PTRSHIFT) == 4 // (_cmd & mask) << (1+PTRSHIFT) = 2 ^ 4 = 16 Add p13, p10, p12, p13, p13 LSL #(1+PTRSHIFT) // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) // do Use p17 and p9 to record the imp and SEL of the current bucket // while bucket--, move the bucket forward by 1: LDP p17, p9, [x13], # -bucket_size // {imp, sel} = *bucket-- If so, call CacheHit to terminate CMP p9, p1 // if (sel! Else {2: CacheHit \Mode // hit: call or return imp //} 3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss; CMP p13, p10} while (bucket >= buckets) // skip 1: } // 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 // Move the bucket to the end of the buckets 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 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 &&buckets = 0 buckets CCMP p13, p12, #0, ne // bucket > first_probed) b.hi4b LLookupEnd\Function: LLookupRecover\Function: b \MissLabelDynamicCopy the code

Find the complete flowchart quickly