This is the 9th day of my participation in the August More Text Challenge. For details, see:August is more challenging

1 Runtime

1.1 What is Runtime

Runtime is translated as runtime, as opposed to compile time. Most iOS developers have heard the word Runtime and are aware of it, but they’re just scratching the surface. They just know about it, and they don’t explore it.

OC language is a dynamic language. It has three characteristics of dynamic language: dynamic typing, dynamic binding, and dynamic loading. The underlying implementation is the familiar and unfamiliar Runtime.

  • The runtime is the runtime environment of an object-oriented programming language (OO). The runtime indicates which program is running during a certain period of time. Runtime is a phase in the life cycle of a computer program. Other phases include compile time, link time, and load time. The simple way to think about it is that the code runs and is loaded into memory.
  • Compile time is, as the name implies, when you are compiling. So what is compiling? The compiler translates your source code into binary code that the machine can recognize. (Just in the general sense, it may actually be translated into some intermediate state of the language. For example, Java has only bytecode recognized by the JVM, and C# has only MSIL recognized by the CLR. There are also linkers, assemblers, or compilers for the sake of understanding.)
  • Compile time is simple translation work, such as checking for keyword errors
  • Lexical analysis, grammatical analysis and the like.
  • If you find an error, the compiler will tell you, so when you normally use Xcode, you hit Build and you start compiling
  • If it’s errors or warning, it’s a compiler check. These errors are called compile-time errors, and the type checking that is done during this process is also called compile-time type checking, or static type checking (where you don’t run the code in memory, you just scan the code as text).

1.2 Runtime uses three ways

Runtime is used in three ways, and its three implementation methods relate to the compilation layer and the underlying layer as shown in the figure

  • Using code above OC, such as [LGPerson Hello]
  • throughNSObjectMethod implementation, for exampleisKindOfClass
  • throughRuntime APIThe underlying method implementation, for exampleclass_getImstanceSize

Compiler in the figure is the compiler, known as LLVM

2. Nature of the OC method

We know that the OC code written in ordinary times is actually implemented by C/C++ code, and then compiled by LLVM compiler, and finally converted into machine language. In the same way, we can use the clang command to compile main. CPP file. What is the essence of the secure method?

2.1 Implementation of the underlying method

In the main function, write the following code

LGPerson *person = [LGPerson alloc]; 
[person sayNB]; 
[person say:@"NB"];
Copy the code

Generate the CPP file, the underlying code is as follows

LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")); 
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB")); 
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_d8842a_mi_3);
Copy the code

As you can see, the essence of the method: objc_msgSend message sends objc_msgSend parameters

  • Message receiver
  • Message body (SEL+ parameters)

2.2 objc_msgSend

In the Build Setting, set Enable Strict Checking of obc_msgSend Calls to NO to turn off the Strict Checking mechanism. Otherwise, the objc_msgSend parameter will be incorrectly imported into the header file

#import <objc/message.h>
Copy the code

In the main function, write the following code

@interface LGPerson : NSObject - (void)sayNB; @end @implementation LGPerson - (void)sayNB{ NSLog(@"666"); } @end int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *person = [LGPerson alloc]; ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB")); } return 0; } ------------------------- // Output: 666Copy the code
  • The result is the same as using the OC method
  • Sel_registerName is the underlying implementation of @selector()

2.2 objc_msgSendSuper

When a subclass calls a superclass method, it can send a message to the superclass using objc_msgSendSuper. Find the definition of objc_msgSendSuper in the objc source code

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);Copy the code
  • Pass a pointer to a structure of type objc_super

Find the definition of the objc_super structure

struct objc_super { 
    /// Specifies an instance of a class. 
    __unsafe_unretained _Nonnull id receiver; 
    /// Specifies the particular superclass of the instance to message. 
    __unsafe_unretained _Nonnull Class super_class; 
    /* super_class is the first class to search */ 
};
Copy the code
  • “Receiver” : indicates the message receiver
  • Super_class passes the first class to be looked up, and if it cannot be found, the search continues layer by layer to the parent class

Call objc_msgSendSuper in the project to define LGeacher, inherited from LGPerson

@interface LGTeacher : LGPerson 
    - (void)sayNB; 
@end 
@implementation LGTeacher 
@end
Copy the code
  • SayNB when method is defined but not implemented

In the main function, write the following code

struct lg_objc_super { __unsafe_unretained _Nonnull id receiver; __unsafe_unretained _Nonnull Class super_class; }; int main(int argc, const char * argv[]) { @autoreleasepool { LGTeacher *teacher = [LGTeacher alloc]; struct lg_objc_super lgSuper; lgSuper.receiver=teacher; lgSuper.super_class=[LGPerson class]; objc_msgSendSuper(&lgSuper, @selector(sayNB)); } return 0; } ------------------------- // Output: 666Copy the code
  • In accordance with theobjc_superSame structure, same definitionlg_objc_superThe structure of the body
  • We already know thatLGTeacherThere is nosayNBMethod, sosuper_classDirect incomingLGPersonThe class object
  • ifsuper_classThe incomingLGTeacherClass method that prints the same result. The difference is that you need to look up one more layer from the parent class.

3 objc_msgSend assembly code

In the objC4-818.2 source code, the assembly instructions vary from system architecture to system architecture. We only explore the assembly code under the most commonly used ARN64 architecture

3.1 objc_msgSend

    ENTRY _objc_msgSend 
    UNWIND _objc_msgSend, NoFrame 

    cmp p0, #0     // nil check and tagged pointer check 
#if SUPPORT_TAGGED_POINTERS 
    b.le LNilOrTagged     // (MSB tagged pointer looks negative) 
#else 
    b.eq LReturnZero 
#endif 
    ldr p13, [x0]     // p13 = isa 
    GetClassFromIsa_p16 p13, 1, x0    // p16 = class
Copy the code
  • ENTRY _objc_msgSend: the entry
  • The P0 register, which stores the message receiver
  • cmp p0, #0: The message receiver is compared to #0
    • In assembly code, there’s no nil, just zeros and ones
    • The #0 comparison here allows you to understand that the message receiver is nil
  • SUPPORT_TAGGED_POINTERS: Real ARM64 architecture,SUPPORT_TAGGED_POINTERSIs defined as 1
  • b.le LNilOrTagger: The value is less than or equal to 0. EnterLNilOrTaggedprocess
  • b.eq LReturnZero: equals 0, and the LReturnZero process is entered
  • Otherwise, the message receiver exists and the code continues to execute
    • ldr p13, [x0]: Take the address of register X0 and assign it to register P13
    • p13: Isa that stores the message receiver
  • Enter theGetClassFromIsa_p16Process, incoming ISA, 1, message receiver

3.2 LNilOrTagged

LNilOrTagged: 
    b.eq LReturnZero // nil check 
    GetTaggedClass 
    b LGetIsaDone
Copy the code
  • B. q: equal to 0, enterLReturnZeroprocess
  • Otherwise, less than zero, the code continues
  • Enter theGetTaggedClassprocess
  • Enter theLGetIsaDoneprocess

3.3 LReturnZero

LReturnZero: 
    // x0 is already zero 
    mov x1, #0 
    movi d0, #0 
    movi d1, #0 
    movi d2, #0 
    movi d3, #0 
    ret
Copy the code
  • Returns nil

3.4 GetClassFromIsa_p16

.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 ExtractISA p16, \src, \auth_address .endif #else // 32-bit raw isa mov p16, \src #endif .endmacroCopy the code
  • The arguments:
    • src:isa
    • needs_auth: 1.
    • auth_address: Message receiver
  • Current inconsistencySUPPORT_INDEXED_ISACondition, skip
  • Conform to the__LP64__The condition is not metneeds_authThe zero condition, enterelsebranch
  • ExtractISA p16, \src, \auth_address: Goes to the ExtractISA process
    • Pass in the P16 register, ISA, message receiver
    • The value of the p16 register, what is stored is not important, because the incomingExtractISAIs assigned to

3.5 ExtractISA

.macro ExtractISA 
    and   $0, $1, #ISA_MASK 
.endmacro
Copy the code
  • $0: P16 register
  • The $1: isa
  • and
    0 . 0,
    1, #ISA_MASK: Store the result of isa&ISA_MASK in register P16

    • P16: addresses of storage objects

3.6 LGetIsaDone

LGetIsaDone: 
    // calls imp or objc_msgSend_uncached 
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
Copy the code
  • objc_msgSendFollow-up process of
    • The message receiver exists, the class object is obtained, and proceedLGetIsaDoneprocess
  • Enter theCacheLookupThe cache lookup process is calledsel-impQuick lookup process
  • Imp orobjc_msgSend_uncached

4 flow chart

— Why get class objects?

  • In the fast lookup process, the cache is stored in the class object, so you must obtain the class object before proceeding with the following process