One, variation method

Both classes and structs in Swift can define methods. One difference, however, is that by default, attributes in structs cannot be modified by their own instance methods.

struct Point {
    var x = 0.0, y = 0.0
    func movePoint(x deltaX: Double, y deltaY: Double) {
        x += deltaX 
        y += deltaY
    }
}
Copy the code

The error warning “Left side of mutating operator isn’t mutable: ‘self’ is immutable” is displayed. You can modify the method by adding the “mutating” keyword in front of it.

// Add the mutating keyword
struct Point {
    var x = 0.0, y = 0.0
    mutating func movePoint(x deltaX: Double, y deltaY: Double) {
        x += deltaX 
        y += deltaY
    }
}
Copy the code

Let’s look at the generated.sil file to see how the mutating method differs from the previous one. Swiftc main. swift-emit -sil), you can use Xcode to open the generated. Sil file.

1. The mutating keyword is not added2. Add the mutating keyword@inout = @inout; @inout = @inout; @inout = @inout An @inout parameter is indirect. The address must be of An initialized object. The current parameter type is indirect. If you look at the arrow %5, let self = Ponit before “mutating” and var self = &point after “mutating”.

From the above analysis, we can deduce the nature of the mutating method: for the mutating method, the passed self is marked as an inout parameter. Whatever happens inside the mutating method affects everything about the external dependency type.

Input and output parameters

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. An input/output formal parameter can be defined by adding an “inout” keyword at the beginning of the formal parameter definition:

var age = 26
func editeAge(age: inout Int)
{
    // The value of external age will be directly affected
    age = 18;
}
Copy the code

Method calls

We know that method calls in OC are essentially objc_msgSend, but what about method calls in Swift?

class Teacher{
    func teach(){
        print("teach")
    }
    func teach1(){
        print("teach1")
    }
    func teach2(){
        print("teach2")}}Copy the code

Let’s take a look at the.sil file generatedYou can see the 3 methods are in vtable, we open the Swift source file. The metadata data structure is as follows

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

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

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
    varSize: UInt32//V-Table
}
Copy the code

1, open Swift source code, we first find metadata

struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> { using StoredPointer = typename Runtime::StoredPointer; using StoredSize = typename Runtime::StoredSize; .private:
  /// An out-of-line Swift-specific description of the type, or null
  /// if this is an artificial subclass. We currently provide no
  /// supported mechanism for making a non-artificial subclass
  /// dynamically.
  // Description of the class
  TargetSignedPointer<Runtime, constTargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description; . }Copy the code

2. Click to enter the class description in TargetClassDescriptor

class TargetClassDescriptor final
: public TargetTypeContextDescriptor<Runtime>,
      public TrailingGenericContextObjects<TargetClassDescriptor<Runtime>,                             TargetTypeGenericContextDescriptorHeader. }Copy the code

We found no vtable inside this class, so we tried to search inside “TargetClassDescriptor”, and we found one

using ClassDescriptor = TargetClassDescriptor<InProcess>;
Copy the code

3. Search globally for ClassDescriptor to locate the genmeta. CPP file

class ClassContextDescriptorBuilder: public TypeContextDescriptorBuilderBase<ClassContextDescriptorBuilder.ClassDecl>, public SILVTableVisitor<ClassContextDescriptorBuilder>
  {
    using super = TypeContextDescriptorBuilderBase;
    ClassDecl *getType() {
      return cast<ClassDecl>(Type);
    }
    // Non-null unless the type is foreign.
    ClassMetadataLayout *MetadataLayout = nullptr;
    Optional<TypeEntityReference> ResilientSuperClassRef;
    SILVTable *VTable;
    bool Resilient;
    bool HasNonoverriddenMethods = false; .void layout() {
      super::layout(); addVTable(); addOverrideTable(); addObjCResilientClassStubInfo(); maybeAddCanonicalMetadataPrespecializations(); }}Copy the code

Layout first calls the superclass “TypeContextDescriptorBuilderBase” layout to create a Descriptor

class TypeContextDescriptorBuilderBase: public ContextDescriptorBuilderBase<Impl> {...void layout() {
      asImpl().computeIdentity();
      super::layout(); asImpl().addName(); asImpl().addAccessFunction(); asImpl().addReflectionFieldDescriptor(); asImpl().addLayoutInfo(); asImpl().addGenericSignature(); asImpl().maybeAddResilientSuperclass(); asImpl().maybeAddMetadataInitialization(); }}Copy the code

Then call your own vatable method. B is the current Descriptor, set the size of the Vtable, then iterate over the vatble, add the function pointer.

void addVTable(){...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

After the above analysis, Descriptor plus the offset is the starting address of the method. Next we can verify the above process by analyzing MACHo.Class, structure, enum Address information is stored in Section64(__TEXT__swift5_types). 0xFFFFFBF4 + 0xBC68 = 0x10000B85C 0x10000 is the address of virtual memory in Swift0xB85C is the memory address in the data DescriptorThe memory address is offset by 13 4-bytes (13 UInt32 in the TargetClassDescriptor class) to get the address 0xB890 of the Vtable (address in macho), plus ASLR to get the actual address0x0000000100044000 + 0xB890 = 0x10004F890 (address of teach function TargetMethodDescriptor)

struct TargetMethodDescriptor {
    MethodDescriptorFlags Flags; / / 4
    TargetRelativeDirectPointer<Runtime, void> Impl; // offset
};
Copy the code

0x10004F890 + 0x4 (Flags) = 0x10004F894 0x10004F894 + 0xFFFFC220 = 0x20004BAB4 (0x10004BAB4 Teach Function Address)

Run the debugging -debug Workflow -always ShowDisassembly command to run the program.Assembly instruction:

  • 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 􏰀 􏰁 􏰂 􏰃 register x0 value 􏰄 􏰅 􏰆 􏰇 assign values to the 􏰈 􏰁 􏰂 􏰃 x1 􏰉Copy the code
  • B: Jump to an address (no return)
  • Bl: Jumps to an address (returns)
  • LDR: Reads a value from memory into a register, as in:
LDR x0, [x1, x2] 􏰀 􏰁 􏰂 􏰃 x1 will register and register 􏰊 􏰁 x2 value 􏰄 􏰅 􏰋 􏰌 􏰙 􏰚 􏰛 􏰜 􏰝 􏰞 􏰟 􏰗 􏰂 􏰛 􏰜 􏰄 􏰅 􏰠 􏰡 together as the address, pick up the memory address register values into 􏰁 x0Copy the code

The address of the teach function is consistent with what we concluded by analyzing the MACHo file. The above is the analysis of the class method storage and call process, that struct method is consistent with the class method?

struct Teacher{
    func teach(){
        print("teach")
    }
    func teach1(){
        print("teach1")
    }
    func teach2(){
        print("teach2")}}Copy the code

We also compile into sil file found no Vtable information, using assembly debugging found is the function directly called, unlike the class. Struct has no inheritance relationship, information is determined when compilation is completed.

4. Influence the distribution mode of function

  • 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.
  • 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 Teacher{ 
     dynamic func teach(){
        print("teach")
    }
}
extension Teacher
{
  // Replace teach, and print "teach3" when calling teach or teach3.
    @_dynamicReplacement(for: teach)
    func teach3(){
        print("teach3")}}Copy the code
  • Objc: this keyword exposes the Swift function to the objC runtime, which is still distributed from the function table.
  • @objc + Dynamic: message distribution mode
  • Extension: Any method defined in extension will not be stored in vatble.