This is the sixth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

When to Insert

So when do you insert? Let’s search ->insert in the source code to see when the insert method is called.

Discovery is not the way to look. Now we need to go to the cache_t insert implementation and run it to find out who called the insert.

You can see that the previous insert step was log_and_fill_cache, so that’s where insert is most likely called, so let’s click on that.

CLS ->cache. Insert (sel, IMP, receiver). It is also possible to find the output in the LLDB input BT, but it is not recommended because it is too ugly and difficult to find.

So that’s where the log_and_fill_cache method is called. Click on the line below log_and_fill_cache to jump to the call to log_and_fill_cache, which is lookUpImpOrForward.

Before the write process, there is a cache read process, objc_msgSend and cache_getImp

Before analyzing, first know what is Runtime

The Runtime is introduced

Runtime is a library that allows you to create objects, examine objects, and modify classes and objects’ methods while a program is running. Runtime comes in two versions — a Legacy version (an earlier version) and a Modern version (the current version), which are different from compile-time

  • Runtime is the process of code running, loading into memory, if there is an error at this point, the program will crash, is a dynamic phase.

  • Compile time is the time when you are compiling. What is compile? The compiler does it for you

The source code is translated into machine-readable code. It is the process of translating the source code into the code that can be recognized by the machine. It is mainly the most basic check and error report for the language, that is, lexical analysis, grammar analysis and so on. It is a static stage

Runtime can be used in the following three ways, and the relationship between the three implementations and the compiler layer and the underlying layer is shown in the figure

  • Through OC code, such as [Person sayNB]

  • Through the NSObject interface, for example, isKindOfClass

  • Through the Runtime API, such as class_getInstanceSize

Let’s define a class LGPerson and add two methods to the class, implementing one of the methods.

Generate an object and call these two methods, and you can see that no errors are reported at compile time.

Let’s run it.

An error. This is the difference between compile time and run time. Take a look at the.m file to see how these three methods are implemented underneath.

As you can see here, the essence of the method is that the objc_msgSend message is sent, and message sending requires two very important parameters. One is the message receiver, and the second is the body of the message. Let’s add parameters to the method and see what happens.

Clang again.

Found the method name has an extra:, and an extra parameter. This means that the body of a method is equal to the method name plus the parameters.

Since calling methods is the same as objc_msgSend, is calling objc_msgSend directly the same as objc_msgSend?

Verify:

Run it:

Person sayNB is equivalent to objc_msgSend(person,sel_registerName(“sayNB”))

You can also pass sel in at sign selector here

Output the result twice, indicating that it is valid.

Note: 1, call objc_msgSend directly, #import

#import

#import

#import

#import

#import

So what happens if a subclass calls a method of its parent class? So let’s take a look at adding the LGTeacher class first, and adding the sayNB implementation, so that LGPerson inherits from LGTeacher.

Clang:

We see the objc_msgSendSuper method at the beginning, but it’s not called.

Let’s see how objc_msgSendSuper is implemented

The parameters required to discover objc_msgSendSuper are

  • struct objc_super * super: an objc_super structure
  • SEL op: a sel
  • .: Parameters (may not exist)

SEL and parameter we know what it is, let’s find out what objc_super is made of.

We don’t even look at __! OBJC2__, so all you need to do is look at receiver and super_class.

Let’s try calling objc_msgSendSuper in main.

Run it:

We find that both successfully call sayHello in the parent LGTeacher. So here, we can make a guess: method calls first look in the class, and if not found in the class, look in the class’s parent class. Next, let’s explore the source implementation of objc_msgSend

Objc_msgSend assembly source code

Image from link:Style_ yueyue.

Let’s put an endpoint where the method is called, because where the method is called is going to call the objc_msgSend method, how it’s going to work.

Run the program here and open debug-Debug Workflow-always ShowdisAssembly in the Xcode toolbar.

Set a breakpoint on the line of objc_msgSend, continue running, and hold control and click Step in.

Look at objc_msgSend in libobjc.a.dylib, and then search for objc_msgSend in the source code.

Because we’re looking for assembly, so let’s look at.s files. We see that there are different types of objc-msg files in different architectures, and here we are looking only at the real machine case, which is objc-msgm-arm64.s.

Look at this ENTRY _objc_msgSend, click on it, and you see this code:

//---- Message sending -- assembler entry --objc_msgSend is to get the isa information of the recipient
ENTRY _objc_msgSend 
/ / -- no window
    UNWIND _objc_msgSend, NoFrame 
    
//---- p0 = null; p0 = receiver
    cmp p0, #0          // nil check and tagged pointer check 
//---- le less than -- supports tagGedPointer (small object type) flows
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        // (MSB tagged pointer looks negative)
#else
//---- p0 = 0, return null
    b.eq    LReturnZero 
#endif 
//---- p0 indicates the existing process of the receiver
//---- remove isa from the address pointed to by register x0 and store it in register P13.
    ldr p13, [x0]       // p13 = isa 
//---- in 64-bit architecture p16 = isa (p13) & ISA_MASK, take out shiftcls information, get the class information
    GetClassFromIsa_p16 p13     // p16 = class 
LGetIsaDone:
    // calls imp or objc_msgSend_uncached 
//---- if isa exists, go to the CacheLookup process, which is called sel-imp quick lookup process
    CacheLookup NORMAL, _objc_msgSend

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
//---- = null, returns 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

Refer to Style_ month month.

Let’s take a look at this compilation:

If p0 is the address of the message receiver, then the address of the person is 0. If p0 is the address of the message receiver, then the address of the person is 0.

cmp p0, #0
Copy the code

Le less than, support the process of TagGedPointer (small object type)

#if SUPPORT_TAGGED_POINTERS
	b.le	LNilOrTagged		// (MSB tagged pointer looks negative)
Copy the code

If P0 is equal to 0, return null.

#else
	b.eq	LReturnZero
Copy the code

Next is the process where P0 (Receiver) exists. Look at the first line.

	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class
Copy the code

What we do here is to store the first address of x0 (receiver), i.e. isa, in p13, and then go to GetClassFromIsa_p16, passing p13,1,xo as parameters.

This is where SUPPORT_INDEXED_ISA is determined. SUPPORT_INDEXED_ISA is not supported here.

So go down to __LP64__. In the __LP64__ process, needs_auth ==1 will go to ExtractISA p16, \ SRC, \auth_address.

.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

.endmacro

Copy the code

Next, search for ExtractISA,

In this case, we will pass the address $1 & ISA_MASK(get class) and store it in $0, which is p16.

so

	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class
Copy the code

This code is used to insert the first address of xo (isa) into the register of P13, and then insert the class into p16 according to p13. Why do you want to get a class? Since the cache insert precedes objc_msgSend, and the cache is in the class, we need to get the class.