One, variation method

1 mutatingThe keyword

In the previous article, you learned that both classes and structs in Swift can define methods. One difference, however, is that by default, a value type attribute cannot be modified by its own instance method.

Self cannot be modified because x and y currently belong to self

Mutating before the func keyword allows you to modify the behavior

  • Think 🤔 : do not addmutatingAccess and AddmutatingWhat is the essential difference between visiting both ❓

1.1 Through GenerationSILFile analysis

  • main.swiftFile to add test code
Struct Point {var x = 0.0, y = 0.0 func test(){let TMP = self.x print(TMP)} mutating func moveBy(x deltaX: Double, y deltaY: Double){ x += deltaX y += deltaY } }Copy the code
  • generateSIL file command:
swiftc -emit-sil main.swift >> main.sil
Copy the code

Obtain the main. Sil file, through the sil file comparative analysis as follows

  • testfunction
// Point.test()
sil hidden @$s4main5PointV4testyyF : $@convention(method) (Point) -> () {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Point):
  debug_value %0 : $Point, let, name "self", argno 1 // id: %1
  ......
}
Copy the code
  • moveByfunction
// Point.moveBy(x:y:)
sil hidden @$s4main5PointV6moveBy1x1yySd_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 // id: %5
  ......
}
Copy the code

1.2 Comparative analysis

  • testThe default argument to the function isPointReceives an instance of the structure (self, which is the value)
    • Assignment process:let self = Poit
  • The default argument to moveBy is@inout Point, receives the address
    • Assignment process:var self = &Poit

Note: Let declarations are immutable while var declarations are mutable

1.3 Example code analysis

var p = Point() // let self = Poit var x1 = p // var sefl = &Poit var x2 = withUnsafePointer(to: &p) {return $0} var x3 = p p.x = 30.0 print(x2.pointe.x) print(x3.x)Copy the code

Output result:

30.0
0.0
Copy the code
  • Changes can be made based on the resultsPointValue:x2Can be modified,x3Do not modify the

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

2. inoutInput/output parameter

2.1 inout SILA document explaining

An @inout parameter is indirect. The address must be of An initialized object.

Example code analysis

var age = 10
func modifyage(_ age: inout Int) {
    age += 1
}
modifyage(&age)
print(age)
Copy the code

Output result:

11
Copy the code
  • inoutAddress passing

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 formal parameters as input and output formal parameters. Adding an inout keyword at the beginning of the formal parameter definition defines an input/output formal parameter

Second, method scheduling

Reviewing OC language, the essence of calling method is message passing, and the underlying method is scheduling through objC_mgSend message mechanism

Addendum: Common assembly instructions

  1. Mov: Copy the value of a register to another register (used only between registers or between registers or constants, not memory addresses).
Mov x1, x0 copies the value of register x0 into register x1Copy the code
  1. Add: Add the value of one register to the value of another register and save the result in the other register
Add the values of registers X1 and x2 and save them to register x0Copy the code
  1. Sub: Subtracts the value of one register from the value of another register and saves the result in the other register
Sub x0, x1, x2 Subtracts the values of registers x1 and x2 and saves them in register x0Copy the code
  1. And: bitwise sums the value of one register with the value of another register and saves the result to another register
And x0, x0, #0x1 save the value of register x0 and constant 1 to register x0Copy the code
  1. ORR: Bitwise or save the value of one register with the value of another register
ORR x0, x0, #0x1 bits or the value of register x0 and constant 1 and saves the result in register x0Copy the code
  1. STR: writes the value in the register to memory
str x0, [x0, x8] ; Save the value in register X0 to stack memory at [x0 + x8]Copy the code
  1. LDR: Reads a value from memory into a register,
LDR x0, [x1, x2] adds the value of register X1 and register x2 as the address, and places the value of the address in register x0Copy the code
  1. CBZ: compares with 0 and jumps if the result is zero (can only jump to subsequent instructions)
  2. CBNZ: Compares with non-zero, and if the result is non-zero then jumps (only to subsequent instructions)
  3. CMP: Comparison instruction
  4. BLR: (branch) Jumps to an address (no return)
  5. Bl: Jumps to an address (returns)
  6. Ret: The subroutine (function call) returns the instruction, the return address has been saved in register LR (X30) by default

2.1 Assembly analysis

Create a Swift project. Define LGTeacher class, define teach function. Type the sign breakpoint as follows:

  • Run the view assembly breakpoints below

  • Assembly analysis, starting with initialization
    • An instruction that calls the initialization of a functionbl, has a return value. Returns theLGTeacherInstance object, the return value of the function is placed inx0In the register
    • The first 8 bytes of x0 are: medata
    • X8 + (0x50) offset function call address
  • It can be concluded that there is an offset operation before a function call:ldr    x8, [x8, #0x50],ldr    x8, [x8, #0x58],ldr    x8, [x8, #0x60]

Find the Metadata schedule based on the function table, determine the function address (Metadata + offset), and execute the function

2.2 SILvalidation

Command to generate the viewController.sil file

Swiftc-emit -silgen - onone-target x86_64-apple-ios13.3-simulator - SDK $(xcrun -- show-sdK-path -- SDK iphonesimulator) ViewController.swift > ./ViewController.silCopy the code

Slide to the bottom of the viewController.sil file

sil_vtable LGTeacher { #LGTeacher.teach: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC5teachyyF // LGTeacher.teach() #LGTeacher.teach1: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC6teach1yyF // LGTeacher.teach1() #LGTeacher.teach2: (LGTeacher) -> () -> () : @$s14ViewController9LGTeacherC6teach2yyF // LGTeacher.teach2() #LGTeacher.init! allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s14ViewController9LGTeacherCACycfC // LGTeacher.__allocating_init() #LGTeacher.deinit! deallocator: @$s14ViewController9LGTeacherCfD // LGTeacher.__deallocating_deinit } sil_vtable ViewController { #ViewController.deinit! deallocator: @$s14ViewControllerAACfD // ViewController.__deallocating_deinit }Copy the code

Vtable is called function table, sil_vtable contains all the functions in the class.

2.3 swiftSource code analysisV-table

The Metdata data structure was discussed in the previous chapter, so where is v-table stored? Let’s start with a review of current data structures

struct Metadata{
    var kind: Int
    var superClass: Any.Type 
    var cacheData: (Int, Int) 
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer 
    var iVarDestroyer: UnsafeRawPointer
}
Copy the code

There’s one thing we need to focus on here, whether it’s Class, Struct, Enum, they all have their own Descriptor, which is a detailed description of the Class

  1. Open Swift source code analysis
  2. Open theMetadata.hfile
  3. findTargetClassMetadataThe structure of the body

  1. Find insideDescriptionvariable

Click on the TargetClassDescriptor to enter the structure, search for the TargetClassDescriptor, and find the alias (ClassDescriptor) as follows:

using ClassDescriptor = TargetClassDescriptor<InProcess>;
Copy the code

Global search ClassDescriptor alias, enter GenMeta. CPP files, locating the ClassContextDescriptorBuilder class, this class is to describe, to create the metadata and Descriptor.

Go to the Layout method

void layout() { assert(! getType()->isForeignReferenceType()); super::layout(); addVTable(); addOverrideTable(); addObjCResilientClassStubInfo(); maybeAddCanonicalMetadataPrespecializations(); }Copy the code

The parent class is implemented in the function. Click super:: Layout () to enter the parent class implementation:

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

Some of the TargetClassDescriptor class member variables have been derived here. Return subclass to addVTable function as follows:

void addVTable() {
      LLVM_DEBUG(
        llvm::dbgs() << "VTable entries for " << getType()->getName() << ":\n";
        for (auto entry : VTableEntries) {
          llvm::dbgs() << "  ";
          entry.print(llvm::dbgs());
          llvm::dbgs() << '\n';
        }
      );

      // Only emit a method lookup function if the class is resilient
      // and has a non-empty vtable, as well as no elided methods.
      if (IGM.hasResilientMetadata(getType(), ResilienceExpansion::Minimal)
          && (HasNonoverriddenMethods || !VTableEntries.empty()))
        IGM.emitMethodLookupFunction(getType());

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

AddVTable function analysis: offset calculates the offset, calls addInt32 to add the offset to B(Descriptor); Finally, the for loop adds a pointer to the function.

The resulting TargetClassDescriptor structure is as follows:

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

2.4 what isMahco

Mach-O is short for Mach Object file format, which is a latticework for MAC and iOS executables. It is similar to the Portable PE Format on Windows and the ELF Format on Linux. Common.o,.a,.dylib, Framework, dyld,.dsym.

MahocFile Format:

Against 2.4.1Header(Header file)

The first is the file header, which indicates that the file is in Mach-O format, specifies the target schema, and some other information about the file attributes, which affects the subsequent structure of the file

2.4.2 Load commands

Load Commands is a table that contains many things. The content includes the location of the region, symbol table, dynamic symbol table, etc.

2.4.3 Datadata

The Data section is mainly responsible for code and Data logging. Mach-o organizes data in segments. A Segment can contain zero or more sections. Depending on which Load Command the Segment is mapped to, sections in a Segment can be interpreted as code, constants, or some other data type. When loaded in memory, memory mapping is done based on Segment.

2.2.4 MachOViewTool presentation projectMach-OThe file format

  • Open the project Products directory

  • Display package contents

  • Drag the MachOView tool to open

  • Mach-OThe file format is as follows

2.5 classfunctionMach-OFile analysis

Execute class function source code

Class LGTeacher{// class LGTeacher? No func teach() {print("teach")} func teach1(){print("teach1")} func teach2(){print("teach2")}} class ViewController: UIViewController{ override func viewDidLoad() { let t = LGTeacher() t.teach() t.teach1() t.teach2() } }Copy the code

2.5.1 Search function inMach-OAddress in file

__swift5_types stores Class, Struct, and Enum descriptors


The first four bytes F4 FB FF FF are LGTeacher Descriptor information, F4 FB FF FF plus 0000BC58 in the pFile field to get Descriptor address information in the Mach-O file

The current ios is in small – end mode. Read data from right to left. Descriptor address information addition equation:

FFFFFBF4+0000BC58 = 0x10000B84C
Copy the code

The address information of the Descriptor is 0x10000B84C, 0x100000000 is the base address of the virtual memory in the Mach-O file

0x10000B84C-0x100000000 = 0xB84C, 0xB84C is the first address of LGTeacher, followed by the contents of the TargetClassDescriptor structure

In the TargetClassDescriptor, there are 12 fields before size, 13 fields after size. the address of teach, teach1, and teach2 in the V-table is as shown in the following figure:

The resulting teach function has an offset of B880 in the Mach-o file

2.5.2 Verifying the current functionMach-OFile address in the program running address

Teach () run address: B880 + ASLR

The breakpoint executes the function, executes the LLDB function image List to get ASLR(the base address of the program to run) 0x0000000104DA8000

The structural address of teach() function is 0x0000000104DA8000 + B880 = 0x104DB3880, as shown in the following figure:

Find the TargetMethodDescriptor structure in the source code, which is the in-memory structure of Swift’s method

Template <typename Runtime> struct TargetMethodDescriptor {/// Flags describing the method. // 4 bytes MethodDescriptorFlags Flags; /// The method implementation. // offset TargetRelativeDirectPointer<Runtime, void> Impl; // TODO: add method types or anything else needed for reflection. };Copy the code
  • ImplAddress:0x104DB3880 + 0x4 + 0xFFFFB9D4 = 0x204DAF258
    • 0x4: Flags
    • 0xFFFFB9D4: Offset (D4 B9 FF FF) Small end mode from right to left
  • teachThe address of the function is0x204DAF2580x100000000 = 0x104DAF258
    • 0x100000000: Base address of virtual memory in mach-o file


Open the assembly debugging mode and read the address of teach function in the assembly, as shown in the figure below:

Finally -> 0x104DAF258 is the address of teach function, V-table is after Descriptor structure, and Swift class methods are stored in v-table function Table.

؏؏☝ᖗ乛◡乛ᖘ☝؏؏

2.5.3 whyMethodData + Offset(offset operation)?

The function is vtableOffset when stored in vtable, so it must be offset when fetched


2.6 structFunction scheduling

Struct LGTeacher{// No func teach() {print("teach")} func teach1(){print("teach1")} func teach2(){print("teach2")}} class ViewController: UIViewController{ override func viewDidLoad() { let t = LGTeacher() t.teach() t.teach1() t.teach2() } }Copy the code

Assembly debugging results:Struct function call, the address of the current function has been determined after compilingStatic distributed

2.7 structextensionFunction scheduling

Struct LGTeacher add extension

struct LGTeacher{ 
    ......
}
extension LGTeacher {
    func teach3(){
        print("teach2")
    }
}
Copy the code

Assembly debugging results:

Teach3 is directly called by address, extension is also statically distributed in struct

2.8 classextensionFunction scheduling

Class LGTeacher add extension implementation class

class LGTeacher{ 
    ......
}
extension LGTeacher {
    func teach3(){
        print("teach2")
    }
}
Copy the code

Assembly debugging results:

teach3Not in theV-TableFunction table, but directly address calls, extension class also belongs toStatic distributed

Method scheduling method summary

type scheduling extension
Value types Static distributed Static distributed
class Function table distribution Static distributed
NSObject subclass Function table distribution Static distributed

Iii. Distribution mode of influence function

3.1 finalThe keyword

  • Final: Functions with the final keyword added cannot be overridden, are statically distributed, do not appear in the VTable, and are not visible to objC runtime.

Assembly debugging results:

SIL analysis (without teach function) :

Added the final keyword, removed from sil_vtable and optimized for direct calls

Properties, methods, and classes do not need to be overloaded during actual development, and final can be used

3.2 dynamicThe keyword

  • Dynamic: The dynamic keyword can be added to all functions to give dynamics to functions of non-objC classes and value types, but the distribution mode is still function table distribution.
Class LGTeacher{// class LGTeacher? Dynamic func teach() {print("teach")}} extension LGTeacher {@_dynamicreplacement (for: teach) func teach3(){ print("teach3") } } class ViewController: UIViewController{ override func viewDidLoad() { let t = LGTeacher() t.teach() } }Copy the code

Output result:

teach3
Copy the code

3.3 @objcThe keyword

  • This keyword exposes the Swift function to the Objc runtime, which is still distributed from the function table.
class LGTeacher : NSObject {
    @objc func teach() {
        print("teach")
    }
}
class ViewController: UIViewController{
    override func viewDidLoad() {
        let t = LGTeacher()
        t.teach()
    }
}
Copy the code

See how it is exposed to Objc

View the apis exposed to Objc

3.4 @objc + dynamicThe keyword

  • The way messages are distributed
Class LGTeacher{// class LGTeacher? @objc dynamic func teach() {print("teach")}} class ViewController: UIViewController{ override func viewDidLoad() { let t = LGTeacher() t.teach() } }Copy the code

Assembly debugging:


4. Function inlining

Function inlining is a compiler optimization technique that optimizes performance by calling a method directly using its content substitution.

4.1 OCProject compiler optimization

int sum(int a, int b) {
    return a + b;
}
int main(int argc, char * argv[]) {
    int a = sum(3, 5);
    NSLog(@"%d", a);
}
Copy the code

Not optimized

Debug Tests in Default None mode

Assembly debugging:

First, store 3 and 5 in W0 and W1 respectively, and then calculate the sum

Optimize Debug by selecting Fastest, Samallest mode to test

Assembly debugging:

As a result, the calculation results are directly stored in W8 and output by calling NSLog function.


4.2 SwiftAn inline function

Optimization Settings

  1. Will ensure that functions are sometimes inlined. This is the default behavior and we don’t need to do anything. The Swift compiler may automatically inline functions as optimizations.


  1. Always – Will ensure that functions are always inlined. This behavior is implemented by prefixing the function with @inline(__always)
@inline(__always) func test() {
    print("test")
}
Copy the code
  1. Never – Will ensure that functions are never inlined. This can be done by prefixing the function with @inline(never)
@inline(never) func test() {
    print("test")
}
Copy the code
  1. If the function is long and you want to avoid increasing the code segment size, use @inline(never).

4.3 PrivateModify functions

If the object is visible only in the declared file, you can decorate it with private or Fileprivate. The compiler checks the private or Fileprivate objects to make sure that there are no other inheritance relationships, and then automatically marks them as final, so that the objects get statically distributed features. (Fileprivate: only accessible in defined source files, private: Definition of access in the declaration)

class LGPerson{ private var sex: Bool private func unpdateSex(){ self.sex = ! self.sex } init(sex innerSex: Bool) { self.sex = innerSex } func test() { self.unpdateSex() } }Copy the code