protocol

2022-07-05  本文已影响0人  f8d1cf28626a

Swift

protocol

案例1

//2-2、通过协议实现:area必须有一个默认实现
protocol Shape {
    var area: Double {get}
}
class Circle: Shape{
    var radius: Double

    init(_ radius: Double) {
        self.radius = radius
    }

    var area: Double{
        get{
            return radius * radius * 3.14
        }
    }
}
class Rectangle: Shape{
    var width, height: Double
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double{
        get{
            return width * height
        }
    }
}

var circle: Shape = Circle.init(10.0)
var rectangle: Shape = Rectangle.init(10.0, 20.0)

var shapes: [Shape] = [circle, rectangle]
for shape in shapes{
    print(shape.area)
}

<!--打印结果-->
314.0
200.0

案例2 协议里面没有定义方法

protocol MyProtocol {
    func teach()
}
extension MyProtocol{
    func teach(){ print("MyProtocol") }
}
class MyClass: MyProtocol{
    func teach(){ print("MyClass") }
}
let object: MyProtocol = MyClass()
object.teach()
let object1: MyClass = MyClass()
object1.teach()

<!--打印结果-->
MyClass
MyClass

案例3 协议里面定义了方法

//如果去掉协议中的声明呢?打印结果是什么
protocol MyProtocol {
}
extension MyProtocol{
    func teach(){ print("MyProtocol") }
}
class MyClass: MyProtocol{
    func teach(){ print("MyClass") }
}
let object: MyProtocol = MyClass()
object.teach()

let object1: MyClass = MyClass()
object1.teach()

<!--打印结果-->
MyProtocol
MyClass

协议的PWT存储位置

我们在分析函数调度时,已经知道了V-Table是存储在metadata中的,那么协议的PWT存储在哪里呢?

protocol Shape {
    var area: Double {get}
}
class Circle: Shape{
    var radius: Double

    init(_ radius: Double) {
        self.radius = radius
    }

    var area: Double{
        get{
            return radius * radius * 3.14
        }
    }
}

var circle: Shape = Circle(10.0)
print(MemoryLayout.size(ofValue: circle))
print(MemoryLayout.stride(ofValue: circle))

var circle1: Circle = Circle(10.0)
print(MemoryLayout.size(ofValue: circle1))
print(MemoryLayout.stride(ofValue: circle1))

<!--打印结果-->
40
40

8
8

其中,SIL官方文档对init_existential_addr的解释如下

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  
  ; s4main6CircleCMa 等价于 type metadata accessor for main.Circle
  %3 = call swiftcc %swift.metadata_response @"$s4main6CircleCMa"(i64 0) #7
  %4 = extractvalue %swift.metadata_response %3, 0
  
  ; s4main6CircleCyACSdcfC 等价于 main.Circle.__allocating_init(Swift.Double) -> main.Circle
  %5 = call swiftcc %T4main6CircleC* @"$s4main6CircleCyACSdcfC"(double 1.000000e+01, %swift.type* swiftself %4)
  
  ; 往一个内存中存储
  ; i32 0, i32 1 结构体不偏移,并选择第二个字段,相当于将metadata放入 T4main5ShapeP结构体的%swift.type*中 ==> type { [24 x i8], metadata, i8** }
  store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main6circleAA5Shape_pvp", i32 0, i32 1), align 8
 
  ; s4main6CircleCAA5ShapeAAWP 等价于 protocol witness table for main.Circle : main.Shape in main 协议目录表,将其放入了 T4main5ShapeP 结构体的i8**中 ==> type { [24 x i8], metadata, PWT }
  store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main6CircleCAA5ShapeAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main6circleAA5Shape_pvp", i32 0, i32 2), align 8
  
  ; s4main6circleAA5Shape_pvp 等价于 main.circle : main.Shape, 将%5放入了 %T4main6CircleC** 中,即 type <{ %swift.refcounted, %TSd }>,相当于将HeapObject放入T4main6CircleC中 ==> type { HeapObject, metadata, PWT }
  ; 将 %T4main6CircleC* %5 实例对象地址 放入了 %T4main6CircleC** 二级指针里,也就意味着实例对象占用8字节,所以放入结构体中就是占用8字节的大小
  store %T4main6CircleC* %5, %T4main6CircleC** bitcast (%T4main5ShapeP* @"$s4main6circleAA5Shape_pvp" to %T4main6CircleC**), align 8
  
  .....

仿写结构

然后通过上述的分析,仿写整个内部结构

<!--1、仿写整个结构-->
struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
    //24 * i8 :因为是8字节读取,所以写成3个指针
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //type 存放metadata,目的是为了找到Value Witness Table 值目录表
    var type: UnsafeRawPointer
    // i8* 存放pwt
    var pwt: UnsafeRawPointer
}
<!--2、定义协议+类-->
protocol Shape {
    var area: Double {get}
}
class Circle: Shape{
    var radius: Double

    init(_ radius: Double) {
        self.radius = radius
    }

    var area: Double{
        get{
            return radius * radius * 3.14
        }
    }
}
//对象类型为协议
var circle: Shape = Circle(10.0)

<!--3、将circle强转为protocolData结构体-->
withUnsafePointer(to: &circle) { ptr in
    ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
        print(pointer.pointee)
    }
}

<!--4、打印结果-->
protocolData(value1: 0x0000000100550100, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x0000000100008180, pwt: 0x0000000100004028)

lldb调试如下,其中value1是HeapObject,type是metadata

而0x0000000100004028可以通过nm + xcrun来验证确实是 PWT

如果将class改成 struct呢?

protocol Shape {
    var area: Double {get}
}
struct Rectangle: Shape{
    var width, height: Double
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double{
        get{
            return width * height
        }
    }
}
//对象类型为协议
var rectangle: Shape = Rectangle(10.0, 20.0)

struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
    //24 * i8 :因为是8字节读取,所以写成3个指针
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //type 存放metadata,目的是为了找到Value Witness Table 值目录表
    var type: UnsafeRawPointer
    // i8* 存放pwt
    var pwt: UnsafeRawPointer
}

//将circle强转为protocolData结构体
withUnsafePointer(to: &rectangle) { ptr in
    ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
        print(pointer.pointee)
    }
}

<!--打印结果-->
protocolData(value1: 0x4024000000000000, value2: 0x4034000000000000, value3: 0x0000000000000000, type: 0x0000000100004098, pwt: 0x0000000100004028)

针对打印结果的lldb调试如下,value1存储10,value2存储20

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  ; 占用16字节
  %3 = call swiftcc { double, double } @"$s4main9RectangleVyACSd_SdtcfC"(double 1.000000e+01, double 2.000000e+01)
  %4 = extractvalue { double, double } %3, 0
  %5 = extractvalue { double, double } %3, 1
  ; 指针类型是 <{ i8**, i64, <{ i32, i32, i32, i32, i32, i32, i32 }>* 
  ; 第一个索引:i32 0 表示需要跨越全局变量 ,其实就是 <{ i8**, i64, <{ i32, i32, i32, i32, i32, i32, i32 }>的首地址
  ; 第二个索引:i32 1 选择结构体的第二个字段
;   存储到结构体的type,即metadata
  store %swift.type* bitcast (i64* getelementptr inbounds (<{ i8**, i64, <{ i32, i32, i32, i32, i32, i32, i32 }>*, i32, i32 }>, <{ i8**, i64, <{ i32, i32, i32, i32, i32, i32, i32 }>*, i32, i32 }>* @"$s4main9RectangleVMf", i32 0, i32 1) to %swift.type*), %swift.type** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main9rectangleAA5Shape_pvp", i32 0, i32 1), align 8
;   使用 s4main9RectangleVAA5ShapeAAWP 结构体来存储
  store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main9RectangleVAA5ShapeAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main9rectangleAA5Shape_pvp", i32 0, i32 2), align 8
;   将double值放入内存中,有偏移,%4 、%5分别的偏移是0、1,是针对 T4main5ShapeP 结构体的偏移
  store double %4, double* getelementptr inbounds (%T4main9RectangleV, %T4main9RectangleV* bitcast (%T4main5ShapeP* @"$s4main9rectangleAA5Shape_pvp" to %T4main9RectangleV*), i32 0, i32 0, i32 0), align 8
  store double %5, double* getelementptr inbounds (%T4main9RectangleV, %T4main9RectangleV* bitcast (%T4main5ShapeP* @"$s4main9rectangleAA5Shape_pvp" to %T4main9RectangleV*), i32 0, i32 1, i32 0), align 8
  ......

如果struct中有3个属性呢?

struct Rectangle: Shape{
    var width, height: Double
    var width1 = 30.0
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double{
        get{
            return width * height
        }
    }
}

<!--打印结果-->
protocolData(value1: 0x4024000000000000, value2: 0x4034000000000000, value3: 0x403e000000000000, type: 0x0000000100004098, pwt: 0x0000000100004028)

从结果中可以看出,是存储在value3

struct Rectangle: Shape{
    var width, height: Double
    var width1 = 30.0
    var height1 = 40.0
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double{
        get{
            return width * height
        }
    }
}

<!--打印结果-->
protocolData(value1: 0x0000000100546a50, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x00000001000040c0, pwt: 0x0000000100004050)

其中value1是一个堆区地址,堆区地址中存储了4个属性的值

协议底层存储结构总结

所以针对协议,其底层的存储结构如图所示:

继续分析

回到下面这个例子中,其中for-in循环能区分不同的area的原因主要是因为 protocol的pwt,pwt其内部也是通过class_method查找,同时在运行过程中存储了metadata,所以可以根据metadata找到对应的v-table,从而完成方法的调用

//2-7、回到2-2的例子中
protocol Shape {
    var area: Double {get}
}
class Circle: Shape{
    var radius: Double

    init(_ radius: Double) {
        self.radius = radius
    }

    var area: Double{
        get{
            return radius * radius * 3.14
        }
    }
}
class Rectangle: Shape{
    var width, height: Double
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double{
        get{
            return width * height
        }
    }
}

var circle: Shape = Circle.init(10.0)
var rectangle: Shape = Rectangle.init(10.0, 20.0)
//所谓的多态:根据具体的类来决定调度的方法
var shapes: [Shape] = [circle, rectangle]
//这里能区分不同area的原因是因为 在protocol中存放了pwt(协议目录表),可以根据这个表来正确调用对应的实现方法(pwt中也是通过class_method查找,同时在运行过程中也记录了metadata,在pwt中通过metadata查找V-Table,从而完成当前方法的调用)
for shape in shapes{
    print(shape.area)
}
protocol Shape {
    var area: Double {get}
}
struct Rectangle: Shape{
    var width, height: Double
    var width1 = 30.0
    var height1 = 40.0
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double{
        get{
            return width * height
        }
    }
}
//对象类型为协议
var rectangle: Shape = Rectangle(10.0, 20.0)
//将其赋值给另一个协议变量
var rectangle1: Shape  = rectangle

<!--查看其内存地址-->
struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
    //24 * i8 :因为是8字节读取,所以写成3个指针
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //type 存放metadata,目的是为了找到Value Witness Table 值目录表
    var type: UnsafeRawPointer
    // i8* 存放pwt
    var pwt: UnsafeRawPointer
}
//将circle强转为protocolData结构体
withUnsafePointer(to: &rectangle) { ptr in
    ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
        print(pointer.pointee)
    }
}

打印结果如下,两个协议变量内存存放的东西是一样的

protocol Shape {
    var width: Double {get set}
    var area: Double {get}
}
struct Rectangle: Shape{
    var width: Double
//    var width, height: Double
    var height: Double
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double{
        get{
            return width * height
        }
    }
}
//对象类型为协议
var rectangle: Shape = Rectangle(10.0, 20.0)
//将其赋值给另一个协议变量
var rectangle1: Shape  = rectangle

//查看其内存结构体
struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
    //24 * i8 :因为是8字节读取,所以写成3个指针
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //type 存放metadata,目的是为了找到Value Witness Table 值目录表
    var type: UnsafeRawPointer
    // i8* 存放pwt
    var pwt: UnsafeRawPointer
}
//将circle强转为protocolData结构体
withUnsafePointer(to: &rectangle) { ptr in
    ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
        print(pointer.pointee)
    }
}
withUnsafePointer(to: &rectangle1) { ptr in
    ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
        print(pointer.pointee)
    }
}

rectangle1.width = 50.0

疑问1:如果将struct修改为class,是否也是写时复制?

如果上述例子中,遵循协议的是类(即struct 改成 class),是否也是写时复制呢?

class Rectangle: Shape{
    var width: Double
//    var width, height: Double
    var height: Double
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double{
        get{
            return width * height
        }
    }
}

lldb调试结果如下,属性值修改前后,堆区地址并没有变化,符合对值类型和引用类型的理解

问题:如果超过24字节,是先存储到value1后发现不够再分配堆区,还是直接分配?

如下所示,struct中定义4个属性

protocol Shape {
    var area: Double {get}
}
class Rectangle: Shape{
    var width: Double
    var height: Double
    var width1: Double
    var height1: Double
    init(_ width: Double, _ height: Double, _ width1: Double, _ height1: Double) {
        self.width = width
        self.height = height
        self.width1 = width1
        self.height1 = height1
    }

    var area: Double{
        get{
            return width * height
        }
    }
}
var rectangle: Shape = Rectangle(10.0, 20.0)

疑问3:如果是存储的值类型是String呢?

如下所示,存储的值类型是String类型,查看其底层存储情况

protocol Shape {
    var area: Double {get}
}
struct Rectangle: Shape{
    var height: String
    init(_ height: String) {
        self.height = height
    }

    var area: Double{
        get{
            return 0
        }
    }
}
var rectangle: Shape = Rectangle("CJL")

//查看其内存结构体
struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
    //24 * i8 :因为是8字节读取,所以写成3个指针
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //type 存放metadata,目的是为了找到Value Witness Table 值目录表
    var type: UnsafeRawPointer
    // i8* 存放pwt
    var pwt: UnsafeRawPointer
}
//将circle强转为protocolData结构体
withUnsafePointer(to: &rectangle) { ptr in
    ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
        print(pointer.pointee)
    }
}

小结

协议在底层的存储结构体如下:

总结

class、struct、enum都可以遵守协议,有以下几点说明:

协议中可以添加属性,有以下两点说明:

协议也可以作为类型,有以下三种场景:

协议的底层存储结构:24字节valueBuffer + vwt(8字节) + pwt(8字节)

上一篇 下一篇

猜你喜欢

热点阅读