• Swift ——协议


    1. 协议与继承

    但是这个时候我们接到一个需求: 要为这两个类创建一个 debug 函数来打印当前类的基本信息。从继承的⻆度来说,我们可能会想到抽取一个公共的基类,当然大家都是动物,人也是动 物。从业务逻辑上来说,这么处理不太合理。可能最直观的办法是对于每一个类都写一个单独的 方法函数。
    在这里插入图片描述
    如果我们对当前代码中的每个类都需要 debug ,那上面这种方法显然是行不通的,于是我们有 了下面的代码

    func debug(subject: Any){ print(.....)
    }
    
    • 1
    • 2

    当然看到这里可能大家也会觉得没有问题,如果我们要具体的描述当前类的具体信息,这个时候 我们还需要引入一个公共的基类,同时我们还需要有一个公共的属性 description 来让子类重 载,这无疑对我们的代码是很强的入侵。
    所以这个时候我们通过一个协议来描述当前类的共同行为,并通过 extension 的方式来对我们 的类进行扩展,这样无疑是很棒的

    extension LGTeacher : CustomStringConvertible {
    var description : String { get { return "LGTeacher: \(age)\(name)" } }
    }
    extension Dog : CustomStringConvertible {
    var description : String { get { return "Dog: \(age)\(name)" } }
    }
    func print(subject: CustomStringConvertible) { let string = data.description
    //to do....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

    2. 协议的基本语法

    • 协议要求一个属性必须明确是 get 或 get 和 set,且必须用var声明也就是变量
    protocol MyProtocol{
        var age: Int{ get set }
        var name: String{ get }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 这里需要注意的一点是:并不是说当前声明 get 的属性一定是计算属性,协议里面声明get说明遵循协议的类要实现属性的get方法。
    class LGTeacher: MyProtocol{
        var age: Int = 18
        var name: String
        init(_ name: String) {
            self.name = name   
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 协议中的异变方法,表示在该方法可以改变其所属的实例,以及该实例的所有属性(用于枚 举和结构体),在为类实现该方法的时候不需要写 mutating 关键字
    protocol Togglable { 
    mutating func toggle()
    }
    
    • 1
    • 2
    • 3
    • 类在实现协议中的初始化器,必须使用 required 关键字修饰初始化器的实现(类的初始化 器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器).如果class 添加final关键字,就不需要添加required了。
    • 类专用协议(通过添加 AnyObject 关键字到协议的继承列表,你就可以限制协议只能被类 类型采纳)
    protocol MyProtocol: AnyObject{}
    
    • 1
    • 可选协议:如果我们不想强制让遵循协议的类类型实现,可以使用 optional 作为前缀 放在协议的定义
    protocol Incrementable{
    optional func increment(by: Int)
    }
    
    • 1
    • 2
    • 3

    3. 协议原理探究

    那么对于协议的方法,会不会合成到类的Vtable当中进行调用呢?将下面的代码编译成sil文件。

    protocol Incrementable{
        func increment(by:Int)
    }
    
    class LGTeacher: Incrementable {
        func increment(by: Int) {
            print(by)
        }
    }
    
    var t = LGTeacher()
    t.increment(by: 20)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    看到这里是class_method, class_method代表着用到了VTable调用方法。
    在这里插入图片描述
    往下看看到了increment在VTable里面。
    在这里插入图片描述
    那么如果把变量声明成协议的类型呢?
    在这里插入图片描述
    这里看到increment依然在Vtable里面。
    在这里插入图片描述
    但是调用方法确是用的witness_method。这里回去witness table(协议见证表)里面找到当前方法的实现。当类遵循了一个协议,实现了方法之后,编译器就会为这个类创建一个witness table,witness table里面就存放着实现方法的编码信息。
    在这里插入图片描述
    往下看到了witness table。
    在这里插入图片描述
    搜索一下,看到这里会去查找LGTeacher对于方法的实现

    在这里插入图片描述
    如果我们给协议的方法添加默认实现,这里还会调用LGTeacher中的实现。
    在这里插入图片描述
    但是,如果去掉了协议里面的声明,那么就会调用协议的默认实现。
    在这里插入图片描述

    • 每个遵守了协议的类,都会有自己的PWT,遵守的协议函数越多,PWT中存储的函数地址就越 多
    • PWT的本质是一个指针数组,第一个元素存储TargetProtocolConformanceDescriptor,其 后面存储的是函数地址
    • PWT的数量与协议数量一致
    • Existential Container 是编译器生成的一种特殊的数据类型,用于管理遵守了相同协议的协 议类型,因为这些类型的内存大小不一致,所以通过当前的 Existential Container 统一管 理
    • 对于小容量的数据,直接存储在 Value Buffer(对于值类型来说,是24字节超过24字节就是大容量的数据)
    • 对于大容量的数据,通过堆区分配,存储堆空间的地址

    4. 写时复制

    那么如果struct的存储属性超过三个,也就是大容量的数据,就会通过对去分配,存储堆空间的地址,这个时候还会有值类型的特性吗?
    这里看到circle的width还是没有改变的。
    在这里插入图片描述
    看到circle1,这里可以看到地址变了,并且width变成了80.
    在这里插入图片描述
    这里看到如果没有改变属性的值,那么指向的地址是一样的。
    在这里插入图片描述

    针对遵循协议的拥有比较大的数据的值类型,Swift采用了一种写时复制的技术,当在修改属性的值的时候,这里会去判断这个堆空间的引用计数,如果引用计数大于1,也就是有多个实例变量在引用这个堆空间的地址,就会触发写时复制,就会在堆空间重新复制一个新的存储空间,并把这个新的空间地址,传给要改变属性值的实例变量,用来保持修改后的实例值,这样做除了会保持值类型的特性外,还减少了内存分配的消耗,避免了创建了一些没有用的存储空间。

  • 相关阅读:
    [kingbase运维之奇怪的现象]
    MySQL表操作—存储
    qtcreator警告“unknown type name qapplication”解决
    每日三题 8.29
    C#/VB.NET 实现Word和ODT文档相互转换
    HashMap源码解析二
    嵌入式硬件VS软件,到底哪个更难?
    uniapp本地打包到Android Studio生成APK文件
    大模型-基于大模型的数据标注
    鲁棒局部均值分解 (RLMD)附Matlab代码
  • 原文地址:https://blog.csdn.net/LinShunIos/article/details/125627035