One, variation method

1.1 Variation method

A value type attribute cannot be modified by its own instance method

Note: moveBy is an instance method, not a builder.

Value types that want to be modified by their own instance methods must be preceded by onemutatingmodified

So what does mutating look like underneath? Let’s add a general method and look through the SIL file:

// Point.move(_:) sil hidden @$s4main5PointV4moveyySdF : $@convention(method) (Double, Point) -> () { // %0 "deltaX" // users: %4, %2 // %1 "self" // user: %3 bb0(%0 : $Double, %1 : $Point): debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %2 debug_value %1 : $Point, let, name "self", argno 2 // id: %3 debug_value %0 : $Double, let, name "u" // id: %4 %5 = tuple () // user: %6 return %5 : $() // id: %6 } // end sil function '$s4main5PointV4moveyySdF' // Point.moveBy(_:_:) sil hidden @$s4main5PointV6moveByyySd_SdtF : $@convention(method) (Double, Double, @inout Point) -> () { // %0 "deltaX" // users: %10, %3 // %1 "deltaY" // users: %20, %4 // %2 "self" // users: %16, %6, %5 bb0(%0 : $Double, %1 : $Double, %2 : $*Point): debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3 debug_value %1 : $Double, let, name "deltaY", argno 2 // id: %4 debug_value_addr %2: $*Point, var, name "self", argno 3 return %26: $() // id: %27 } // end sil function '$s4main5PointV6moveByyySd_SdtF'Copy the code

As we all know, the default method also passes the self argument, look at the code above

The mutating modifier adds the inout keyword to a Point

Move (_) method %1 arguments: $Point, let, name “self”, argno 2 meaning self is let constant

MoveBy (:) method %2 argument: $*Point, var, name “self”, argno 3 meaning self is the var variable value

That is, the self of the mutating method is passed as a pointer, so it can modify the value of the address. (The Ponit argument refers to self, so we can use self in the method)

S4main5PointV4moveyySdF and s4main5PointV6moveByyySd_SdtF are move(:) and moveBy(:_:) functions respectively. Xcrun swift-demangle

The nature of mutant methods: For mutant methods, self passed in is marked as an inout parameter. Whatever happens inside the mutating method affects everything about the external dependency type.

1.2 inout

If we want a function to be able to change the value of a formal parameter, and we want those changes to persist after the function ends, we need to define the formal parameter as inout.

struct Point{
    
    var x = 0.0, y = 0.0
    
    func move(_ deltaX:Double._ deltaY: inout Double){
        
        let u = deltaX
        deltaY = 50}}Copy the code

Look at the SIL:

// Point.move(_:_:) sil hidden @$s4main5PointV4moveyySd_SdztF : $@convention(method) (Double, @inout Double, Point) -> () { // %0 "deltaX" // users: %6, %3 // %1 "deltaY" // users: %9, %4 // %2 "self" // user: %5 bb0(%0 : $Double, %1 : $*Double, %2 : $Point): debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3 debug_value_addr %1 : $*Double, var, name "deltaY", argno 2 // id: %4 debug_value %2 : $Point, let, name "self", argno 3 // id: %5 debug_value %0 : $Double, let, name "u" // id: // end sil function '$s4main5PointV4moveyySd_SdztF'Copy the code

Second, method scheduling

A little prep: register instructions

2.1 Class method scheduling

Here’s the test code:

class CCTeacher{
    
    func teach(a){
        print("teach")}func teach1(a){
        print("teach1")}func teach2(a){
        print("teach2")}}class ViewController: UIViewController{
    override func viewDidLoad(a) {
        let t = CCTeacher()
        t.teach()
        t.teach1()
        t.teach2()
    }
}
Copy the code

Here are two ways to explore class method scheduling

1, LLDB breakpoint, observe register instruction

2. Observe the SIL code

2.1.1 LLDB breakpoint, observe register instruction

Run the real machine with a breakpoint

Observe the breakpoint: just three BLR method jumps, that is, method calls

The instance object of the function is placed in the X0 register

mov    x20, x0

Copy the value of x0 into x20, or copy the instance object into x20

ldr    x8, [x20]

Take the value in x20, put it in x8, so now x8 is the instance object, take the first 8 bytes of the instance object, because the register is 64 bits, hold 8 bytes, and what is the first 8 bytes in x20? Yes, the metadata

ldr    x8, [x8, #0x50]

Add the values of x8 and #0x50 (offset) and place them in x8

blr    x8

Finally, call x8

Metadata -> determine the address of the function (Metadata + offset) -> execute the function

2.1.2 Function table V_table

Take a closer look at the offset added before the call:

There are 8 bytes between #0x50 #0x58 #0x60! Then these 8 bytes are the size of the function pointer, and they are contiguous memory space in memory, which can lead to swift’s first function scheduling mode: scheduling based on function table V_table

Compile a SIL to see:

sil_vtable CCTeacher { #CCTeacher.teach: (CCTeacher) -> () -> () : @$s14ViewController9CCTeacherC5teachyyF // CCTeacher.teach() #CCTeacher.teach1: (CCTeacher) -> () -> () : @$s14ViewController9CCTeacherC6teach1yyF // CCTeacher.teach1() #CCTeacher.teach2: (CCTeacher) -> () -> () : @$s14ViewController9CCTeacherC6teach2yyF // CCTeacher.teach2() #CCTeacher.init! allocator: (CCTeacher.Type) -> () -> CCTeacher : @$s14ViewController9CCTeacherCACycfC // CCTeacher.__allocating_init() #CCTeacher.deinit! deallocator: @$s14ViewController9CCTeacherCfD // CCTeacher.__deallocating_deinit }Copy the code

In the SIL file, the sil_vtable is the function table, which lists the functions of the CCTeacher class

2.1.3 Querying VTABLE from source Code

The Swift class contains a metadata containing a member that is:

var typeDescriptor: UnsafeMutableRawPointer
Copy the code

Classes, structs, and enumerations all have this member variable that holds a description of themselves

Find the definition of typeDescriptor in the source code, and the search process is as follows:

  • Find HeapMetadata in the heapObject.h file
using HeapMetadata = TargetHeapMetadata<InProcess>;
Copy the code

HeapMetadata is an alias of TargetHeapMetadata

  • Enter theTargetHeapMetadataThe structure of the body

Find:

ConstTargetMetadataPointer<Runtime, TargetClassDescriptor> Description;
Copy the code

At this point, Description is a class that’s TargetClassDescriptor, and inherits a bunch of stuff

After the members are sorted, you can get something like this:

struct TargetClassDescriptor{

    var flags: UInt32
    var parent: UInt32
    var name: Int32
    var accessFunctionPointer: Int32
    var fieldDescriptor: Int32
    var superClassType: Int32
    var metadataNegativeSizeInWords: UInt32
    var metadataPositiveSizeInWords: UInt32
    var numImmediateMembers: UInt32
    var numFields: UInt32
    var fieldOffsetVectorOffset: UInt32
    var Offset: UInt32
    var size: UInt32
    //V-Table
}
Copy the code

And then you scroll down and you see that TargetClassDescriptor has a single name: ClassDescriptor

using ClassDescriptor = TargetClassDescriptor<InProcess>;
Copy the code

Global search: ClassDescriptor, you can find the genmeta. CPP file, this is where the metadata is generated

Enters GenMeta. CPP files, positioning to ClassContextDescriptorBuilder, that’s it, to create the metadata and Descriptor

Scroll down to the Layout () method

void layout(a) {
    super::layout(a);addVTable(a);addOverrideTable(a);addObjCResilientClassStubInfo(a); }Copy the code

It calls super:: Layout () first, so let’s look at its parent class:

void layout(a) {
    asImpl().computeIdentity(a); super::layout(a);asImpl().addName(a);asImpl().addAccessFunction(a);asImpl().addReflectionFieldDescriptor(a);asImpl().addLayoutInfo(a);asImpl().addGenericSignature(a);asImpl().maybeAddResilientSuperclass(a);asImpl().maybeAddMetadataInitialization(a); }Copy the code

It also calls void Layout () :

void layout(a) {
    asImpl().addFlags(a);asImpl().addParent(a); }Copy the code

Well, if you string it together, does it ring a bell?

It’s basically all right, so this layout, this layout, is just building TargetClassDescriptor

Look at the addVTable() function:

void addVTable(a) {
    if (VTableEntries.empty())
        return;

    if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal))
        IGM.emitMethodLookupFunction(getType());

    auto offset = MetadataLayout->hasResilientSuperclass()? MetadataLayout->getRelativeVTableOffset()
                 : MetadataLayout->getStaticVTableOffset(a); B.addInt32(offset / IGM.getPointerSize());
    B.addInt32(VTableEntries.size());
      
    for (auto fn : VTableEntries)
        emitMethodDescriptor(fn);
}
Copy the code

B. adddint32, calculated the offset and added it to B. B added the size of vtable and traversed the VTableEntries array. Call emitMethodDescriptor(SILDeclRef fn) and add the function pointer fn, and the B is the current TargetClassDescriptor, in other words, add something to B, You just add something to the TargetClassDescriptor

2.1.4 Analyzing class methods in Mach-O

Open MachOView to view Mach-O and look at the base address of virtual memory: 0x100000000

See: Section64(__Text,__swift5_types) section, where you store structure, enumerations, class descriptors, the first four bytes store class descriptors, So 90 FB FF FF is the address information of the CCTeacher Descriptor, so how do you verify that it stores the address information?

Add the preceding four bytes, which are offsets in The Mach-O, to get the memory address of the Descriptor in the Mach-O file

Since this address is in small-endian mode, read from right to left: FF FF FB 90

FFFFFB90 + 0000BBCC = 0x10000B75C

Then take the result of this calculation and subtract the virtual memory base address to get the memory address in the Mach – O file Descriptor

0x10000B75C0x100000000 = B75C

Const (); const ();

In other words, starting at 50, find a place where you can store the contents of the Descriptor, which means that 50 is the first address of the structure of the TargetClassDescriptor, and the contents after that should correspond to the contents of the structure, so in that case, I count 12 4-bytes against TargetClassDescriptor and go straight to vtable (because vtable is number 13) :

2.1.5 Verify the Mach-O analysis through the program

The above analysis shows that teach() corresponds to an offset address of 000B790 in Mach-o

Command image list

this0x0000000100a8c000isThe base address at which the program is runThat isASLRIs the starting address of the project after it is loaded into memory.

Add the offset 000B790 of teach() in Mach -o to the base where the program is run

0x0000000100a8c000 + 000B790 = 0x100A97790

That is to say,0x100A97790Point to this structure:

Look at the source code again. LookTargetMethodDescriptorThis structure, it represents the structure of the Swift method FlagsIdentify what type of method this is

class MethodDescriptorFlags {
public:
  typedef uint32_t int_type;
  enum class Kind {
    Method,
    Init,
    Getter,
    Setter,
    ModifyCoroutine,
    ReadCoroutine,
  };
Copy the code

The Impl is a relative pointer, an Offset

So that means that 0x100A97790 that we just figured out points to this structure of TargetMethodDescriptor, and since the first member of this structure is Flags, we’re going to offset it, Flags is an enumeration of uint32, so it’s 4 bytes, we’re going to offset it by 4 bytes, Impl = Impl; Impl = Impl; Impl = Impl;

Method address = 0x100A97790 + FLAGS (4 bytes) + IMPL

What is the IMPL content?

The first four bytes are flags. The next four bytes are the IMPL. The IMPL is FF, FF, C2, 5C

0x100A97790 + 4 + FFFFC25C = 0x200A939F0

Finally, subtract the base address of mach-O virtual memory 0x100000000

0x200A939F00x100000000 = 0x100A939F0

0x100A939F0 is the address of teach()

Assembly interrupt point verification:

2.2 Struct method scheduling

Replace the above class with a struct and run a breakpoint

struct CCTeacher{
    
    func teach(a){
        print("teach")}func teach1(a){
        print("teach1")}func teach2(a){
        print("teach2")}}class ViewController: UIViewController{
    override func viewDidLoad(a) {
        let t = CCTeacher()
        t.teach()
        t.teach1()
        t.teach2()
    }
}
Copy the code

Good guy, direct BL function address, that is to call the function directly.

This means that, after compiling, the address of the function is determined. Because the structure has no inheritance relationship, it does not need to create a separate memory space to record the location of each function, so the function address is determined after the compilation link, directly optimized for static calls

Iii. Function distribution mode

3.1 the final

Two points to note:

  • befinalModified class that cannot be inherited
  • befinalModifier methods, subscripts, and attributes are not allowed to be overridden for byfinalDecorated methods are issued statically directly and will not be added tovtableAnd rightobjcNot visible at runtime.

3.2 the dynamic

  • Functions can be addeddynamicKeyword, assigned to functions that are not objC classes and value typesdynamic, but the distribution is stillFunction table vtableDistributing.
  • But with@_dynamicReplacement(for:)Together with
class CCTeacher {
    
    dynamic func teach(a){
        print("teach")}}extension CCTeacher {
    Teach3 () teach3()
    @_dynamicReplacement(for:teach())
    func teach3(a){
        print("teach3")}}let t = CCTeacher()
t.teach()
// Print the result: Teach3
Copy the code

3.3 @objc / @objc + dynamic

  • @objcKeywords can beSwift functionExposed to theObjc runtime, thus using the Runtime method, such as method exchange, but the function table vtable is still distributed, that is to say, oc class still cannot use the swift function.
class CCTeacher {
    
    @objc dynamic func teach(a){
        print("teach")}}Copy the code
  • To make the SWIFT function available to the OC class, the SWIFT class inherits fromNSObject

@objc + dynamicThe mode of message dispatch, also known as messaging in oc.