但是这个时候我们接到一个需求: 要为这两个类创建一个 debug 函数来打印当前类的基本信息。从继承的⻆度来说,我们可能会想到抽取一个公共的基类,当然大家都是动物,人也是动 物。从业务逻辑上来说,这么处理不太合理。可能最直观的办法是对于每一个类都写一个单独的 方法函数。
如果我们对当前代码中的每个类都需要 debug ,那上面这种方法显然是行不通的,于是我们有 了下面的代码
func debug(subject: Any){ print(.....)
}
当然看到这里可能大家也会觉得没有问题,如果我们要具体的描述当前类的具体信息,这个时候 我们还需要引入一个公共的基类,同时我们还需要有一个公共的属性 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....
}
这里我们可以稍微的总结一下:
protocol MyProtocol{
var age: Int{ get set }
var name: String{ get }
}
class LGTeacher: MyProtocol{
var age: Int = 18
var name: String
init(_ name: String) {
self.name = name
}
}
protocol Togglable {
mutating func toggle()
}
protocol MyProtocol: AnyObject{}
protocol Incrementable{
optional func increment(by: Int)
}
那么对于协议的方法,会不会合成到类的Vtable当中进行调用呢?将下面的代码编译成sil文件。
protocol Incrementable{
func increment(by:Int)
}
class LGTeacher: Incrementable {
func increment(by: Int) {
print(by)
}
}
var t = LGTeacher()
t.increment(by: 20)
看到这里是class_method, class_method代表着用到了VTable调用方法。
往下看看到了increment在VTable里面。
那么如果把变量声明成协议的类型呢?
这里看到increment依然在Vtable里面。
但是调用方法确是用的witness_method。这里回去witness table(协议见证表)里面找到当前方法的实现。当类遵循了一个协议,实现了方法之后,编译器就会为这个类创建一个witness table,witness table里面就存放着实现方法的编码信息。
往下看到了witness table。
搜索一下,看到这里会去查找LGTeacher对于方法的实现
如果我们给协议的方法添加默认实现,这里还会调用LGTeacher中的实现。
但是,如果去掉了协议里面的声明,那么就会调用协议的默认实现。
那么如果struct的存储属性超过三个,也就是大容量的数据,就会通过对去分配,存储堆空间的地址,这个时候还会有值类型的特性吗?
这里看到circle的width还是没有改变的。
看到circle1,这里可以看到地址变了,并且width变成了80.
这里看到如果没有改变属性的值,那么指向的地址是一样的。
针对遵循协议的拥有比较大的数据的值类型,Swift采用了一种写时复制的技术,当在修改属性的值的时候,这里会去判断这个堆空间的引用计数,如果引用计数大于1,也就是有多个实例变量在引用这个堆空间的地址,就会触发写时复制,就会在堆空间重新复制一个新的存储空间,并把这个新的空间地址,传给要改变属性值的实例变量,用来保持修改后的实例值,这样做除了会保持值类型的特性外,还减少了内存分配的消耗,避免了创建了一些没有用的存储空间。