iOS开发-Swift进阶之协议Protocol!

>[**swift进阶总汇**](https://www.jianshu.com/p/c00fa675d7d5) 本文主要分析protocol的用法及底层存储结构 ![](https://upload-images.jianshu.io/upload_images/25331915-b8fcb7529b9d9c67.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 协议的基本用法 * 【语法格式】:协议的语法格式 ``` //协议的语法格式 protocol MyProtocol { //body } ``` * `class、struct、enum`都可以遵守协议,如果需要`遵守多个协议`,可以使`用逗号分隔` ``` //1-2、class、struct、enum都可以遵守协议,如果需要遵守多个协议,可以使用逗号分隔 struct CJLTeacher: Protocol1, Protocol2 { //body } ``` * 如果class中有`superClass`,一般是放在遵守的协议之前 ``` //1-3、如果class中有superClass,一般是放在遵守的协议之前 struct CJLTeacher: NSObject, Protocol1, Protocol2 { //body } ``` 协议中添加属性 * 协议中可以添加属性,但是需要注意一下几点: * 1、协议同时要求一个`属性必须`明确是`可读的/可读可写的` * 属性要求定义为`变量属性`,即使用`var`而不是let ``` protocol CJLTest { var age: Int {get set} } ``` 协议中定义方法 * 在协议中定义方法,只需要定义当前方法的名称、参数列表和返回值 * 在具体的类中遵守协议,并实现协议中的方法 ``` protocol MyProtocol { func doSomething() static func teach() } class CJLTeacher: MyProtocol{ func doSomething() { print("CJLTeacher doSomething") } static func teach() { print("teach") } } var t = CJLTeacher() t.doSomething() CJLTeacher.teach() ``` * 协议中也可以`定义初始化方法`,当实现初始化器时,必须使用`required`关键字 ``` protocol MyProtocol { init(age: Int) } class CJLTeacher: MyProtocol{ var age: Int required init(age: Int) { self.age = age } } ``` * 如果一个协议只能被`类`实现,需要协议继承自`AnyObject`。如果此时`结构体`遵守该协议,会`报错` ## 协议进阶 - 将协议作为类型 协议除了上述的基本用法,还有以下几种用法: * 1、作为`函数、方法或者初始化`程序中的`参数类型或者返回值` * 2、作为`常量、变量或属性的类型` * 3、作为`数组、字典或`者`其他容器`中项目的`类型` 通过继承基类实现 下面一段代码的打印结果是什么?(通过`继承基类`实现) ``` class Shape{ var area: Double{ get{ return 0 } } } class Circle: Shape{ var radius: Double init(_ radius: Double) { self.radius = radius } override 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 } override 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 ``` 对于数组来说,当前的大小是固定的,因为当前存放的都是`引用类型`(即占`8`字节),其存储结构如下所示 通过协议实现 >作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:[130 595 548](https://jq.qq.com/?_wv=1027&k=L3kztZno),不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!) * 上述代码的实现是通过继承基类,即`基类中的area必须有一个默认实现`,也可以通过`协议`来替代当前代码的书写方式 ``` //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 ``` 当数组中的元素指定的Shape是类时,数组中存储的都是引用类型的地址,那么问题来了,如果数组指定的Shape是一个协议时,数组中存储的是什么? * 如果Shape协议提供了一个默认实现,此时的打印是什么? ``` protocol Shape { } extension Shape{ var area: Double { get{return 0} } } 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) print(circle.area) 0.0 ``` 打印`0.0`的原因是因为`在Extension中声明的方法是静态调用`,即在编译链接后当前底阿妈的地址就已经确定了,我们是`无法重写`的。这个可以通过SIL代码来验证 ### 协议示例代码分析 下面通过一个简单的代码来分析SIL * 【示例1】:下面代码的打印结果是什么? ``` 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 ``` 打印一样的原因是因为在`MyProtocol`协议中有teach方法的声明 * 查看`SIL`中两种方式的调用有什么不同? * 定义为`MyProtocol`类型的对象`object`,方法teach的调用在底层是通过`witness_method`调用,即`通过PWT(协议目录表)获取对应的函数地址`,其内部也是`通过类的函数表查找进行调用` * 定义为`MyClass`类型的对象`object1`,方法teach的调用在底层是通过`类的函数表`来查找函数,主要是基于类的实际类型 其中,协议目录表和函数表如下所示 查看协议中`teach`方法具体实现的SIL代码,在`内部调用`的是`MyClass类的函数表中的teach方法` * 【示例2】:如果去掉`MyProtocol`协议中teach方法的声明,打印结果是什么? ``` //如果去掉协议中的声明呢?打印结果是什么 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 ``` 打印不一致的根本原因是`MyProtocol`协议扩展中实现的teach方法不能被类重写,相当于这是两个方法,并不是同一个 * 查看底层的SIL代码 * 第一个打印`MyProtocol`,是因为调用的是协议扩展中的teach方法,这个方法的地址是在编译时期就已经确定的,即`通过静态函数地址调度` * 第二个打印`MyClass`,同上个例子一样,是类的函数表调用 查看SIL中的`witness_table`,其中已经没有teach方法 * 声明在`Protocol`中的方法,在底层会存储在`PWT`,PWT中的方法也是通过`class_method`,去`类的V-Table`中找到对应的方法的调度。 * 如果没有声明在`Protocol`中的函数,只是通过`Extension`提供了一个默认实现,其函数地址在编译过程中就已经确定了,对于遵守协议的类来说,这种方法是无法重写的 ### 协议的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 ``` * 首先通过`lldb`调试如下 * 查看对应的SIL代码,比往常的代码多了一步`init_existential_addr`,可以理解为:使用了包含`Circle`的`existential container`来初始化`circle`引用的内存。通俗来说就是将`circle`包装了存入`existential container`初始化的内存 其中,SIL官方文档对`init_existential_addr`的解释如下 其中的`existential container`是编译器生成的一种特殊的数据类型,也用于管理遵守了相同协议的协议类型。因为这些数据类型的内存空间尺寸不同,使用`existential container`进行管理可以实现存储一致性 * 通过`IR`代码,分析如下 ``` 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 ,相当于将HeapObject放入T4main6CircleC中 ==> type { HeapObject, metadata, PWT } ; 将 %T4main6CircleC* %5 实例对象地址 放入了 %T4main6CircleC** 二级指针里,也就意味着实例对象占用8字节,所以放入结构体中就是占用8字节的大小 store %T4main6CircleC* %5, %T4main6CircleC** bitcast (%T4main5ShapeP* @"$s4main6circleAA5Shape_pvp" to %T4main6CircleC**), align 8 ..... ``` 仿写结构 然后通过上述的分析,仿写整个内部结构 ``` 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 } 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) withUnsafePointer(to: &circle) { ptr in ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in print(pointer.pointee) } } protocolData(value1: 0x0000000100550100, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x0000000100008180, pwt: 0x0000000100004028) ``` lldb调试如下,其中`value1`是`HeapObject`,`type`是`metadata` 而`0x0000000100004028`可以通过`nm + xcrun`来验证确实是?`PWT` 如果将class改成 struct呢? * 如果其中的`类改成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` * 查看其IR代码 ``` 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 ; 指针类型是 * ; 第一个索引:i32 0 表示需要跨越全局变量 ,其实就是 的首地址 ; 第二个索引:i32 1 选择结构体的第二个字段 ; 存储到结构体的type,即metadata store %swift.type* bitcast (i64* getelementptr inbounds (*, 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的结构体属性是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中有4个属性呢? ``` 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个属性的值 协议底层存储结构总结 所以针对协议,其底层的存储结构如图所示: * 1、`前24`个字节,主要用于`存储遵循了协议的class/struct的属性值`,如果24字节不够存储,会在堆区开辟一个内存空间用于存储,24字节中的前8个字节存储堆区地址(如果超出24,是`直接分配堆区空间,然后存储值`,并不是先存储值,然后发现不够再分配堆区空间) * 2、`后16`个字节分别用于存储?`vwt`(值目录表)、`pwt`(协议目录表) 继续分析 回到下面这个例子中,其中`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) } ``` * 继续回到`struct`的例子,将其赋值给另一个变量,其内存存放的是否是一样的? ``` 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) } } ``` 打印结果如下,两个协议变量`内存存放的东西是一样`的 * 如果`修改rectangle1的width属性的值`(需要将width属性声明到protocol),修改后的代码如下 ``` 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 ``` 通过lldb调试发现,在`rectangle1`变量修改`width`之后,其存储数据的堆区地址发生了变化。这就是所谓的`写时复制`【当复制时,并没有值的修改,所以两个变量指向同一个堆区内存,当第二个变量修改了属性值时,会将原本堆区内存的值拷贝到一个新的堆区内存,并进行值的修改】 疑问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) ``` * 查看其IR代码,从代码中可以看出,是`先分配堆区空间,再将属性值存储到堆区空间中` 疑问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) } } ``` * 查看其IR代码 * lldb调试如下,底层也是`通过value存储` ### 总结 协议在底层的存储结构体如下: * 前面的24字节,官方称为`Value Buffer`,用来存储当前的值 * 如果超过`Value Buffer`最大容量(24字节) * `值类型`?采用?`copy-write`,即拷贝时拷贝content整体,当修改值时,会先检查引用计数,如果`引用计数大于1,会开辟新的堆的内存空间`,然后将修改的值放入新的空间中,其目的是为了`提高内存的利用率,降低堆区的内存消耗,从而实现性能的提升` * `引用类型`?则是`使用同一个堆区地址`,因为其拷贝变量与原变量是共享状态 ## 总结 >作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:[130 595 548](https://jq.qq.com/?_wv=1027&k=L3kztZno),不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!) * class、struct、enum都可以遵守协议,有以下几点说明: * 1、多个协议之间需要使`用逗号分隔` * 2、如果class中有`superClass`,一般`放在协议之前` * 协议中可以添加`属性`,有以下两点说明: * 1、属性必须明确是?`可读(get)/可读可写(get + set)`的 * 2、属性使用`var`修饰 * 协议中可以`定义方法`,只需要定义当前方法的`名称+参数列表+返回值`,其具体实现可以通过协议的`extension实现`,或者在`遵守协议时实现` * 协议中也可以`定义初始化方法`,当实现初始化器时,必须使用`required`关键字 * 如果协议只能被class实现,需要协议继承自`AnyObject` * 协议也可以作为类型,有以下三种场景: * 1、作为`函数、方法或者初始化`程序中的`参数类型或者返回值` * 2、作为`常量、变量或属性的类型` * 3、作为`数组、字典或`者`其他容器`中项目的`类型` * 协议的底层存储结构:`24字节valueBuffer + vwt(8字节) + pwt(8字节)` * (1)`值类型`?采用?`copy-write` * (2)`引用类型`?则是`使用同一个堆区地址` * 1、`前24`个字节,官方称为`Value Buffer`,主要用于`存储遵循了协议的class/struct的属性值` * 2、如果超过`Value Buffer`最大容量 * 3、`后16`个字节分别用于存储?`vwt`(值目录表)、`pwt`(协议目录表)

iOS开发-Swift进阶之协议Protocol!

上一篇:Android---adb整机测试命令(了解)


下一篇:php根据身份证号码计算年龄