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 CodeDirect 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 methodisObjc_msgSend Sends messages
  • OCA method is calledEquivalent to runtimeobjc_msgSendandobjc_msgSendSuperMessage 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

The flow chart of objc_msgSend