Swift class and Structure (part 1) understand the difference between class and structure, and analyze the structure of Swift class. Let’s take a peek at the methods in classes and structures.

1. Mutating method

In Swift, the modified attribute of a value type will directly modify the memory of the instance, which is equivalent to modifying its own value. Therefore, the attribute of a value type cannot be modified by its own method, which needs to be modified with the mutating keyword

Why is it possible to modify its own value by adding mutating? Take a peek at how Mutating works with the following code

Sil analysis

Generate the SIL file using the following command

Swiftc-emit -sil viewController. swift >./ viewController. sil // Use swiftc-emit - sil-target when compiling UIKit content to sil X86_64-apple-ios15.0-simulator -sdk $(xcrun --show-sdk-path -- SDK iphonesimulator) viewController. swift > ./ViewController.silCopy the code
struct StructSwagger{
    var sex: Bool = false
    var age: Int = 18
    func test(_ age:Int){
        var age = age
        age = 20
        print(age)
    }
    mutating func test1(_ newage:Int){
        age += newage
    }
}
Copy the code
  • The test function

  • Test1 function

The two methods are illustrated by comparison

The two approaches are most obvious in the following two aspects

  • The first box mark of the two functions indicates that the instance function defaults to a different type of the self argument
    • testThe incoming isStructSwaggerType, is the instance object itself
    • test1The incoming is@inout StructSwaggerType, ‘is the address of the instance object
  • As you can see from the second box tag of the two functions, the line parameter types declared at the bottom of the function are different
    • inoutThe argument will declare onevarconstant
    • The ordinary parameters areletconstant

These two differences can be represented by the following pseudocode

//test1  
let self = StructSwagger

//test1
var self = &StructSwagger
Copy the code

In a non-mutating instance method, the value itself is passed in, and the default value is let, so it cannot be modified. The self passed in the mutating instance method is marked as an inout parameter. The self passed in is a pointer to the instance and is declared with a var so that the properties of the value type can be modified internally.

Class instance methods

2.1 Assembly Verification

Start with x0. Since the X0 register is typically used to store the return value of a function, x0 may be__alloc_initThe return value of the function, the instance object, sets the breakpoint atmov x20, x0The location of the passregister read You can read the stored value and get that it is indeed an instance objectmetaldataSo x0, x20 are all instances of class objects

(lldb) register read x0
      x8 = 0x00000001005e8ab0  type metadata for Swagg3r.Swagger
Copy the code

LDR x8 [X20] fetch the metadata of the instance object and store it in x8. LDR x8 [x20] fetch the metadata of the instance object and store it in X8. Through the above assembly analysis, it can be seen that the positions of the three boxes are the positions of the three method calls. We can clearly see that the offsets of the three methods are 0x50,0x58 and 0x60, which are three consecutive positions

Summary: Class instance methods are called via metadata and then an offset to find the method address

Assemble common instructions

  • CBZ: compares with 0 and jumps if the result is zero (can only jump to subsequent instructions)
  • CBNZ: Compares with non-zero, and if the result is non-zero then jumps (only to subsequent instructions)
  • CMP: Comparison instruction
  • Br: Jump to an address (no return)
  • BLR: Jump to an address (return)
  • Ret: The subroutine (function call) returns the instruction, the return address has been saved in register LR (X30) by default
  • Mov: to copy the value of a register to another register (used only to transfer values between registers or constants, not memory addresses), as in:
Mov x1, x0 assign register x0 to register x1 ˙Copy the code
  • Add: To add the value of one register to the value of another register and save the result in another register, as:
Add x0, x1, x2 Assign the value of register x1 plus x2 to x0Copy the code
  • Sub: Subtract the value of one register from the value of another register and save the result in another register:
Sub x0, x1, x2 Assign the value of register x1 minus x2 to x0Copy the code
  • And: to place the value of one register with the value of another register and save the result to another register, as:
And x0, x0, #0x1; and x0, x0, #0x1Copy the code
  • ORR: To place the value of one register with the value of another register or save the result to another register, as:
ORR x0, x0, #0x1 assigns register x0 and constant 1 to x0 bit by bit or postCopy the code
  • STR: Writes a value from a register to memory, as:
str x0, [x0, x8] ; Save register x0 to stack memory [x0 + x8]Copy the code
  • LDR: Reads a value from memory into a register, as in:
LDR x0, [x1, x2] adds the values of register X1 and register x2 as the address, and stores the value of this memory address into X0Copy the code

2.2 sil verification

Generate the SIL file using the following command

Swiftc-emit -sil viewController. swift >./ viewController. sil // Use swiftc-emit - sil-target when compiling UIKit content to sil X86_64-apple-ios15.0-simulator -sdk $(xcrun --show-sdk-path -- SDK iphonesimulator) viewController. swift > ./ViewController.silCopy the code

Sil_vtable = sil_vtable = sil_vtable = sil_vtable = sil_vtable

Struct method calls

struct Swagger {
    func test() {
        print("test")
    }
    func test1() {
        print("test1")
    }
    func test2() {
        print("test2")
    }
}
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let t = Swagger()
        t.test()
        t.test1()
        t.test2()
    }
}
Copy the code

In Swift, the way to call a structure is to call it directly with the address of the function, including the initialization method. Swift is a static language, many things can be determined at run time, so you can directly get the address of the function to call, this call form is calledStatic distributed

4. Scheduling of other functions

4.1 Extension function call

As you can see, both class and struct methods in extension are distributed statically

4.2 Classes inherited from NSObject

The sil_vtable derived from the NSObject class is verified in the same way as the pure Swift class. The instance methods of the class are stored in the Vtable and distributed through the Vtable function table, while the extension methods are not stored and are called statically

4.3 Method scheduling summary

4.4 Impact of Keywords on Distribution Modes

class classSwagger{
    final func finalSwag() { }

    static func staticSwag() { }

    dynamic func dynamicSwag() { }

    @objc func objcSwag() { }

    @objc dynamic func objcDynamicSwag() { }
}
Copy the code

  • final

Functions that add the final keyword cannot be overridden, are statically distributed, do not appear in the Vtable, and are not visible to the OBJC runtime

  • static

Static methods do not exist in the vTable and are sent statically

  • dynamic

To make functions that are not objC classes and value types dynamic, the modified methods are stored in the Vtable and dispatched through the function table

  • @objc

This keyword can expose the Swift function to the OBJC runtime to interact with the OC, and the modified method will be stored in the Vtable and distributed through the function table

  • @objc + dynamic

@objc + Dynamic becomes the mode of message dispatch – the messaging mechanism in OC

5. Inline functions

An inline function is a compiler optimization technique that optimizes performance by calling a method directly using its content substitution

Swift inline

  • Inline functions in Swift are the default behavior, we do not need to do anything, the Swift compiler may automatically inline functions as optimizations
  • Always – Will ensure that functions are always inlined. This behavior is implemented by prefixing the function with @inline(__always)
  • Never – Will ensure that functions are never inlined. This can be done by prefixing the function with @inline(never)
  • If the function is long and you want to avoid increasing the code segment size, use @inline(never).

Xcode set

Optimization operations for private

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)