“This is my fourth day of participating in the First Challenge 2022. For more details: First Challenge 2022.”

I. Agreement and Inheritance

Class CXTeacher {var name = "chenxi"} class Dog {var name = "sticky rice" var type = "tedddy"}Copy the code

For example, in the code above, we have a requirement to add a debug function to both classes to print information about the current class. From an inheritance point of view, we might want to extract a common base class, where of course both classes are animals, and humans are animals. But from a business logic point of view, this does not make sense. So the most intuitive approach is to write a separate debug function for each class.

Class CXTeacher {var age = 10 var name = "chenxi" func debug(){print("")}} class Dog {var name = "sticky rice" var type = Func debug(){print("")}}Copy the code

If we needed to debug every class in the current code, this would obviously not work, so we have the following code:

func debug(subject: Any){
    print("")
}
Copy the code

This may not be a problem, but if we want to describe the specific information of the current class, we also need to introduce a common base class, and we also need to have a common attribute description to make the subclass reload, which is definitely a strong intrusion on our code.

So it would be much better to use a protocol to describe the common behavior of the current class and extend our class with extension.

extension CXTeacher : CustomStringConvertible {
    var description : String { get { return "LGTeacher: \(age)\(name)" } }
}
extension Dog : CustomStringConvertible {
    var description : String { get { return "Dog: \(name)\(type)" } }
}

func print(subject: CustomStringConvertible) {
    let string = subject.description
    print(string)
}
Copy the code

Here we can summarize a little bit:

  • classEssentially what is an object defined
  • protocolIt essentially defines how an object behaves

2. Basic syntax of the agreement

  • The protocol requires that an attribute be explicitly GET or GET and set
protocol MyProtocol {
    var age: Int{ get set } 
    var name: String{ get }
}
Copy the code

One thing to note here is that the property currently declared for GET is not necessarily a computed property

class LGTeacher: MyProtocol{ 
    var age: Int = 18
    var name: String
    init(_ name: String) {
        self.name = name
    }
}
Copy the code
  • Mutating method in the protocol, which means that the method can change the instance it belongs to, and all the attributes of the instance (used for nulls and structs). When implementing the method for the class, you do not need to write the mutating keyword
protocol Togglable {
    mutating func toggle()
}

class CXTeacher: Togglable {
    func toggle() {
        print(#function)
    }
}

struct CXPerson: Togglable {
    mutating func toggle() {
        print(#function)
    }
}
Copy the code
  • Class initializers in the implementation protocol must be decorated with the required keyword (the required modifier precedes class initializers to indicate that all subclasses of the class must implement the initializer).

protocol MyProtocol {
    init(_ age: Int)
}

class CXPerson: MyProtocol {
    var age = 10
    required init(_ age: Int) {
        self.age = age
    }
}
Copy the code

There is a special case here where if CXPerson is not inheritable, then the required modifier is fine.

protocol MyProtocol {
    init(_ age: Int)
}

final class CXPerson: MyProtocol {
    var age = 10
    init(_ age: Int) {
        self.age = age
    }
}
Copy the code
  • Class-specific protocols (by adding the AnyObject keyword to the protocol’s inheritance list, you can restrict the protocol to being adopted by the class type)

  • Optional protocol: If we do not want to enforce a protocol-compliant class type implementation, we can prefix the definition of the protocol with optional.
@objc protocol Incrementable { @objc optional func increment(by: Int) -> Int } class CXPerson: Incrementable { } let p: Incrementable = CXPerson() Incrementable = CXPerson() (by: 10)Copy the code

3. Explore the principle of agreement

SIL code analysis protocol scheduling

In the previous article, we learned that class method calls are scheduled by v-table. What about scheduling when classes implement methods in the protocol? Here we compile the SWIFT code into SIL code to take a look.

  • swiftcode
protocol Incrementable {
    func increment(by: Int)
}

class CXPerson: Incrementable {
    func increment(by: Int) {
        print(by)
    }
}

let p: CXPerson = CXPerson()
p.increment(by: 10)
Copy the code
  • SILThe code analysis

The first step is to find the main function. Here we can see that the increment function is scheduled by class_method. Below we will see the explanation of class_method in the official document.

According to the documentation, class_method is scheduled in v-table mode.

Here we search s4main8CXPersonC9increment2byySi_tF.

Increment is declared in the V-table. Let’s change the swift code and declare p as Incrementable to see if the scheduling is the same.

  • swiftcode
protocol Incrementable {
    func increment(by: Int)
}

class CXPerson: Incrementable {
    func increment(by: Int) {
        print(by)
    }
}

let p: Incrementable = CXPerson()
p.increment(by: 10)
Copy the code
  • SILThe code analysis

Witness_method = witness_method > witness_method (witness_method);

The documentation basically says that the implementation of the current method will be found by looking up the witness-table of the current class. Witness-table is called protocol witness table. When a class implements a protocol and the methods in the protocol, the compiler creates an witness-table for the current class. Witness-table records the coding information of the protocol methods implemented by the current class.

We do see in the SIL code sil_witness_table, here we through search s4main8CXPersonCAA13IncrementableA2aDP9increment2byySi_tFTW look at the agreement method is how to schedule.

Here will pass s4main8CXPersonCAA13IncrementableA2aDP9increment2byySi_tFTW method to find the specific type, also is our CXPerson class implement the increment method. That is, when a variable is declared as a protocol type, the compiler makes a layer of bridge through witny-table to find the actual type and implementation of the variable, and complete the method scheduling.

Analyze protocol method scheduling by assembly code

  • swiftcode
protocol Incrementable {
    func increment(by: Int)
}

class CXPerson: Incrementable {
    func increment(by: Int) {
        print(by)
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let p: Incrementable = CXPerson()
        p.increment(by: 10)
    }
}
Copy the code
  • Assembly code analysis

X2 stores witness-table, and x2 offsets 0x8 to increment X8. At the BLR X8 breakpoint, the fn + Control + F7 shortcut keys are used to step into the increment function.

As you can see here, there is a jump through x8 offset 0x50, which is the actual execution address of increment. You can also see from the FN + Control + F7 shortcut keys that the INCREMENT method of CXPerson is executed.