美文网首页Swiftswift
Swift 协议介绍

Swift 协议介绍

作者: 晨曦的简书 | 来源:发表于2022-01-28 18:52 被阅读0次

    一、协议与继承

    class CXTeacher {
        var age = 10
        var name = "chenxi"
    }
    
    class Dog {
        var name = "糯米"
        var type = "泰迪"
    }
    

    例如如上代码,这个时候我们有一个需求,要为这两个类添加一个 debug 函数来打印当前 类的信息。从继承的⻆度来说,我们可能会想到抽取一个公共的基类,当然这里两个类都是动物,人也是动物。但是从业务逻辑上来说,这么处理不太合理。所以最直观的办法是对于每一个类都写一个单独的 debug 函数。

    class CXTeacher {
        var age = 10
        var name = "chenxi"
        
        func debug(){
            print("")
        }
    }
    
    class Dog {
        var name = "糯米"
        var type = "泰迪"
        
        func debug(){
            print("")
        }
    }
    

    如果我们对当前代码中的每个类都需要 debug,那上面这种方法显然是行不通的,于是我们有 了下面的代码:

    func debug(subject: Any){
        print("")
    }
    

    看到这里可能大家也会觉得没有问题,但是如果我们要具体的描述当前类的具体信息,这个时候 我们还需要引入一个公共的基类,同时我们还需要有一个公共的属性 description 来让子类重 载,这无疑对我们的代码是很强的入侵。

    所以这个时候我们通过一个协议来描述当前类的共同行为,并通过 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)
    }
    

    这里我们可以稍微的总结一下:

    • class 本质上定义了一个对象是什么
    • protocol 本质上定义了一个对象有哪些行为

    二、协议的基本语法

    • 协议要求一个属性必须明确是 get 或 get 和 set
    protocol MyProtocol {
        var age: Int{ get set } 
        var name: String{ get }
    }
    

    这里需要注意的一点是:并不是说当前声明 get 的属性一定是计算属性

    class LGTeacher: MyProtocol{ 
        var age: Int = 18
        var name: String
        init(_ name: String) {
            self.name = name
        }
    }
    
    • 协议中的异变方法,表示在该方法可以改变其所属的实例,以及该实例的所有属性(用于枚 举和结构体),在为类实现该方法的时候不需要写 mutating 关键字
    protocol Togglable {
        mutating func toggle()
    }
    
    class CXTeacher: Togglable {
        func toggle() {
            print(#function)
        }
    }
    
    struct CXPerson: Togglable {
        mutating func toggle() {
            print(#function)
        }
    }
    
    • 类在实现协议中的初始化器,必须使用 required 关键字修饰初始化器的实现(类的初始化器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器)
    protocol MyProtocol {
        init(_ age: Int)
    }
    
    class CXPerson: MyProtocol {
        var age = 10
        required init(_ age: Int) {
            self.age = age
        }
    }
    

    这里有一种特殊情况,如果 CXPerson 是不可被继承的,那么不加 required 修饰也没问题。

    protocol MyProtocol {
        init(_ age: Int)
    }
    
    final class CXPerson: MyProtocol {
        var age = 10
        init(_ age: Int) {
            self.age = age
        }
    }
    
    • 类专用协议(通过添加 AnyObject 关键字到协议的继承列表,你就可以限制协议只能被类 类型采纳)
    • 可选协议:如果我们不想强制让遵循协议的类类型实现,可以使用 optional 作为前缀 放在协议的定义。
    @objc protocol Incrementable {
         @objc optional func increment(by: Int) -> Int
    }
    
    class CXPerson: Incrementable {
    
    }
    
    let p: Incrementable = CXPerson()
    //这里CXPerson没实现increment方法这样调用的话也不会出错
    p.increment?(by: 10)
    

    三、协议原理探究

    SIL 代码分析协议调度方式

    在前面的文章中我们已经了解到类的方法调用是通过 V-table(函数表)来调度的,那么让类来实现协议中的方法的时候调度方式是怎么样的呢?这里我们将 swift 代码编译 成 SIL 代码来看一下。

    • swift 代码
    protocol Incrementable {
        func increment(by: Int)
    }
    
    class CXPerson: Incrementable {
        func increment(by: Int) {
            print(by)
        }
    }
    
    let p: CXPerson = CXPerson()
    p.increment(by: 10)
    
    • SIL 代码分析

    第一步我们先找到 main 函数,在这里可以看到 increment 函数是通过 class_method 方式进行调度的,下面我们在官方文档看一下对 class_method 的解释。

    通过文档介绍可以知道 class_method 是通过 v-table 的方式进行调度的。

    这里我们搜索 s4main8CXPersonC9increment2byySi_tF

    这里可以看到 increment 被声明在了 v-table 中。下面我们对 swift 代码做下修改,将 p 声明成 Incrementable 类型再来看一下调度方式是否一样。

    • swift 代码
    protocol Incrementable {
        func increment(by: Int)
    }
    
    class CXPerson: Incrementable {
        func increment(by: Int) {
            print(by)
        }
    }
    
    let p: Incrementable = CXPerson()
    p.increment(by: 10)
    
    • SIL 代码分析

    这里在 main 函数中可以看到 increment 函数的调度变成了 _method,所以我们先在官方文档看一下 witness_method 的介绍。

    文档的大致意思是讲会通过查到当前类的 witness-table 来找到当前方法的实现。这里 witness-table 叫作协议见证表,当一个类准了一个协议并且实现了协议中的方法,编译器就会为当前类创业一个 witness-tablewitness-table 就记录了当前类实现协议方法的编码信息。

    SIL 代码中我们确实看到了 sil_witness_table,这里我们通过搜索 s4main8CXPersonCAA13IncrementableA2aDP9increment2byySi_tFTW 来看一下协议方法是如何调度的。

    这里会通过 s4main8CXPersonCAA13IncrementableA2aDP9increment2byySi_tFTW 来查找具体的方法类型,也就是我们在 CXPerson 类中实现 increment 方法 。也就是说当变量被声明成协议类型的时候,编译器会通过 witness-table 做一层桥接, 最终找到变量的具体实际类型及具体实现,并完成方法的调度。

    通过汇编代码分析协议方法的调度

    • swift 代码
    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)
        }
    }
    
    • 汇编代码分析

    这里可以看到 x2 存储的就是 witness-tablex2 偏移 0x8 得到 x8,也就是 increment 函数地址。在 blr x8 断点这里操作 fn + control + F7 快捷键,也就是指令单步执行,进入到 increment 函数的具体实现。

    这里可以看到,有经过了一层跳转,通过 x8 偏移 0x50,这个时候才是 increment 函数的真正执行地址。通过 fn + control + F7 快捷键也可以看到执行了 CXPersonincrement 方法。

    相关文章

      网友评论

        本文标题:Swift 协议介绍

        本文链接:https://www.haomeiwen.com/subject/zzinhrtx.html