In Swift, when we talk about value types we usually think of structs and classes as reference types, so why is a structure a value type and a class a reference type? This article explores the difference between value types and reference types in terms of structs and class triggers

Value types

  • Here is an example of value types:

    func valueTest(a) {
        var age1: Int = 18
        var age2: Int = 20 
        var age3: Int = age1
        age3 = 26
    }
    valueTest()
    Copy the code
    • Print the memory address of the three temporary variables as follows:



    • In the printed result, you can see that the addresses of age1 and age3 are different, and their values are different after age3 is assigned, indicating that the process of age3 = age1 is equivalent to deep copy, indicating that age is the value type

    • The memory starting with 0x7 represents the stack area, and the stack area memory is contiguous, refer to the memory partition and layout

The structure of the body

  • Let’s define two constructs:

    struct WSPerson {
        var age: Int = 18
    }
    
    struct WSTeacher {
        var age: Int
    }
    
    / / initialization
    var person = WSPerson(a)var teacher = WSTeacher(age: )
    Copy the code
    • Each member of the two structures has a value, but the initialization method makes a differenceSilFile viewing source code



    • inSilIn the fileWSPersonThere are twoinitDelta function, one of them is rightageThe value is assigned, so when a member variable has a value, it does not have to be assigned a new value. whileWSTeacherInitialize theinitThere is only one method, so the two structures are initialized differently.

Structs with initial value types are analyzed below

Struct is value type analysis

  • Define the structure as follows:

    struct WSPerson {
        var age1: Int = 18
        var age2: Int = 22
    }
    
    var wushuang = WSPerson(a)var tony = wushuang
    tony.age1 = 19
    Copy the code
    • The output of the two structure objects is as follows:



    • Through printing, it is found that the values of the two are different from the address, which stores the value of the member directly

  • Sil analysis was conducted for Tony and Wushuang



    • inmainIn the function, it’s mostly done firstwushuangCreate and copy a copy totonyLet’s seewushuangCreate the core logicinit



    • The main code to create memory is inThe stack areaOpen up memory, and the processing of member variables, soStructs are value types

Summary: 1. Structure open memory stack area 2. Structure assignment is deep copy

Reference types

  • Let’s take a look at several ways to initialize a class:

    class WSCat {
        var age: Int
        init(age: Int) {
            self.age = age
        }
    }
    class WSDog {
        var age: Int?
    }
    
    class WSTeacher {
        var age: Int = 18
    }
    
    var cat = WSCat(age: 2)
    var dog = WSDog(a)var teacher = WSTeacher(a)Copy the code
    • When a property in a class has a value or an optional type, you don’t have to override the init method; When the property has no value, the init method must be overridden

    • Then print the teacher related information as follows:



    • It can be seen from the printed content that teacher is a pointer, which points to the first address of the class in the heap, and the relevant information of the class can be read from the class

Class is reference type analysis

  • Teacher2 create teacher2 and assign teacher to teacher2.



    • Although the address of the new objects is different, they point to the same heap memory, so they operate on the same memory space, which we can verify by printing the age value of both:



    • The result is that both ages have the same value, so the assignment of the class object is a shallow copy, and thus class is a reference type

Value types nested reference types

  • Change the code to value type nested reference type as follows:

    class WSDog {
        var dogAge: Int = 3
    }
    
    struct WSPerson {
        var age: Int = 18
        var dog = WSDog()}var person1 = WSPerson(a)var person2 = person1
    person2.dog.dogAge = 5
    Copy the code
    • Printing two objects results in the following:



    • Although person is a value type, dog inside is a reference type, and they operate on the same piece of memory, so the dog.dogAge value in both objects is the same

Mutating & inout

  • When defining a struct, instance variables are not allowed to be modified in the struct’s methods, as shown below:



    • Change the value of the modified variable in the method:
    struct WSPerson {
        var age: Int = 0
      
        func add(_ count: Int) {
            print(count)
        }
    }
    Copy the code
    • Generate the Sil file and view itaddMethods:



    • inaddIn the method, there’s aletThe type ofselfThat is, the structure is immutable if you change itageChanging the value of a member variable in a method is an error.
  • If self is received as a mutable type, the result will not report an error:

    struct WSPerson {
        var age: Int = 0
      
        func add(_ count: Int) {
            var s = self
            s.age + = count
        }
    }
    
    var person = WSPerson()
    person.add(3)
    print(person.age)
    Copy the code
    • The print result is as follows:



    • Since the struct is a value type, sosIt’s a deep copy. The value changed issAnd, inpersonObject is independent, so the print is still0
  • To change the value of the instance variable, append the method to mutating:



    • generateSilFile and viewaddmethods



    • Observation discovery method addedmutatingAfter that, there are the following changes:
        1. Parameters of theWSPersonincreasedinoutmodified
        1. selfYou’re accessing the address
        1. selfisvarThe variable type
    • So the change in value directly modifiespersonAddress, so it can be modified successfully
  • We don’t know what effect the above inout has. Let’s analyze the case below



    • Since the parameters are of let type, they cannot be modified. In this case, you can add inout to modify the parameters:



    • After intout is added, the parameter passed is the address, so the parameter can be modified

Method dispatch

  • In the above analysis we know that a structure is a value type, so where is its method? We’ll cover method storage and invocation of structs and classes

The structure of the body

  • It has the following structure

    struct WSPerson {
        func speak(a) {
            print(" Hello word ")}}let ws = WSPerson()
    ws.speak()
    Copy the code
    • Look at the assembly code when calling the speak method:



    • In assembly, it is a direct callq call to address 0x100003D20, that is, to call the speak method. This call is also known as a static call, because there is no method in the structure, so it will be directly read in the code segment (_TEXT). Now open the project’s MachO file in MachOView



    • In the code snippet, we find the assembly code to call the Speak method

  • When viewing the assembly at breakpoint, symbols are displayed behind the address of callq. Symbols exist in the String Table, which can be read according to the information in the Symbol Table. The Symbol Table query process is as follows:



    • The binary of symbols in the string table looks like this:



    • ldanddyldWill be inlinkRead the symbol table
  • We can use the nm + MachO path to view the symbolic information of the project:



  • You can restore symbols using the xcRun swift-demangle + symbol:



class

  • Let’s look at the class method call, first define a class and call method:

    class WSCat {
        func sleep1(a) { print(" sleeping 1.. ")}func sleep2(a) { print(" sleeping 2.. ")}func sleep3(a) { print(" sleeping 3.. ")}func sleep4(a) { print(" sleeping 4.. ")}func sleep5(a) { print(" sleeping 5.. ")}}let ragdoll = WSCat()
    ragdoll.sleep1()
    ragdoll.sleep2()
    ragdoll.sleep3()
    ragdoll.sleep4()
    ragdoll.sleep5()
    Copy the code
    • Create a breakpoint at the calling method and look at the assembly:



    • The address of the callq is a contiguous piece of memory.



  • To produce the Sil file and view it:



    • inSilThe order of methods is the same as in the assembly, and they all existvtableMedium, let’s go toswiftCheck the source codevtableWhat’s going on at the bottom
  • Search for initClassVTable in swift source and get the following code:



    • It is mainly through the pointer translation to obtain the method name and associationimp.

extension

  • How are methods scheduled in Extension? The WSCat class is defined next, and the Ragdoll class inherits the WSCat class

    class WSCat {
        func sleep1(a) { print(" sleeping 1.. ")}func sleep2(a) { print(" sleeping 2.. ")}func sleep3(a) { print(" sleeping 3.. ")}func sleep4(a) { print(" sleeping 4.. ")}}extension WSCat {
        func sleep5(a) { print(" sleeping 5.. ")}}class Ragdoll: WSCat {}var cat = Ragdoll()
    cat.sleep5()
    Copy the code
    • The sleep5 method in this extension is scheduled using the vtable file.



    • In Sil, you can see that Ragdoll inherits the other WSCat methods, but not the sleep5 method. If sleep5 is in the Vtable of WSCat, Ragdoll will inherit it. However, if the subclass wants to add methods, the compiler will not be able to determine whether to add methods to the parent class or subclass because the method is added by pointer translation in the Vtable. Therefore, the method in extension can only be called directly



  • At this point we can conclude that the method calls in Extension are direct calls

conclusion

  • The structure of the bodyThe method of scheduling is by addressDirect call
  • classThe method of scheduling is passedvtableTo carry out the
  • extensionThe method inDirect callthe

Final, @objc, dynamic

  • The influence of several keywords on method scheduling is studied below

final

  • Let’s define a WSCat class with a method that uses final modifier

    class WSCat {
        final func sleep1(a) { print(" sleeping 1.. ")}func sleep2(a) { print(" sleeping 2.. ")}func sleep3(a) { print(" sleeping 3.. ")}func sleep4(a) { print(" sleeping 4.. ")}}Copy the code
    • Then combining withSilandassemblyAnalysis method scheduling





  • So the conclusion is that the final modifier method is called directly

@objc

  • Add the @objc keyword to the WSCat method:

    class WSCat {
        @objc func sleep1(a) { print(" sleeping 1.. ")}func sleep2(a) { print(" sleeping 2.. ")}func sleep3(a) { print(" sleeping 3.. ")}func sleep4(a) { print(" sleeping 4.. ")}}Copy the code
    • In combination withSilAnd assembly analysis:







    • althoughvtableThere aresleep1Method, but the scheduling method is different from the above, which is calledFunction table scheduling
  • Can the method adding @objc be called by OC? Not really, we can look at the mixed header file first



    • It turns out that there’s no WSCat information in the header file, because in order to call OC, the class has to inherit NSObject, so you inherit NSObject and then you look at the header file



  • Now that the class inherits NSObject, what happens to the Sil file





    • By observationSilThere are twosleep1Method, one to giveSwiftUse,@objcSupply of marksOCuse

dynamic

  • willWSCatAdd a method indynamicmodified
    class WSCat {
        dynamic func sleep1(a) { print(" sleeping 1.. ")}func sleep2(a) { print(" sleeping 2.. ")}func sleep3(a) { print(" sleeping 3.. ")}func sleep4(a) { print(" sleeping 4.. ")}}Copy the code
    • throughSilAnd the compilation analysis is knowndynamicThe modified function scheduling mode isFunction table scheduling

Methods exchange

  • In the Sil file at the sleep1 function location, you can see that it is marked dynamically_replacable



    • Indicates that it is dynamically modifiable, that is, if the class inheritsNSObject, then it can proceedmethod-swizzling
  • The @_dynamicreplacement (for: function symbol called) function is used for method exchange in Swift. The code is as follows:

    class WSCat: NSObject {
        dynamic func sleep1(a) { print(" sleeping 1.. ")}func sleep2(a) { print(" sleeping 2.. ")}func sleep3(a) { print(" sleeping 3.. ")}func sleep4(a) { print(" sleeping 4.. ")}}extension WSCat {
        @_dynamicReplacement(for: sleep1)
        func eat(a) {  print(" have fish ")}// The swap function
    }
    
    var cat = WSCat()
    cat.sleep1()
    Copy the code
    • The print result is as follows:



@objc+dynamic

  • Add the @objc keyword to the front of the dynamic method as follows:

    class WSCat: NSObject {
        @objc dynamic func sleep1(a) { print(" sleeping 1.. ")}func sleep2(a) { print(" sleeping 2.. ")}func sleep3(a) { print(" sleeping 3.. ")}func sleep4(a) { print(" sleeping 4.. ")}}Copy the code
  • Call sleep1 and look at the assembly:



    • As a result, the method calling method becomesobjc_msgSend

conclusion

  • Struct is a value type whose function scheduling is called directly, that is, static scheduling

    • If you want to change the value of an instance variable in a function, you need to add it in front of the functionMutatingmodified
  • Class is a reference type, and its function scheduling is through the Vtable function, that is, dynamic scheduling

  • Functions in Extension are called directly, that is, statically scheduled

  • The final modified functions are called directly, that is, statically scheduled

  • The @objc modified function is function table scheduling, and if the method needs to be used in OC, the class needs to inherit NSObject

  • Dynamic modification of the function scheduling is the function table scheduling, it is dynamic can be modified, can be method-swizzling

    • @objc+dynamiThe modified function is passedobjc_msgSendTo invoke the
  • If a parameter in a function wants to be changed, you need to add the inout keyword in front of the type of the parameter and pass in the address of the parameter when called