本文主要分析protocol的用法及底層存儲(chǔ)結(jié)構(gòu)
協(xié)議的基本用法
- 【語(yǔ)法格式】:協(xié)議的語(yǔ)法格式
//協(xié)議的語(yǔ)法格式
protocol MyProtocol {
//body
}
-
class矫膨、struct粤铭、enum
都可以遵守協(xié)議,如果需要遵守多個(gè)協(xié)議
痪署,可以使用逗號(hào)分隔
//1-2、class啼辣、struct、enum都可以遵守協(xié)議,如果需要遵守多個(gè)協(xié)議丽惭,可以使用逗號(hào)分隔
struct CJLTeacher: Protocol1, Protocol2 {
//body
}
- 如果class中有
superClass
叫潦,一般是放在遵守的協(xié)議之前
//1-3、如果class中有superClass祝懂,一般是放在遵守的協(xié)議之前
struct CJLTeacher: NSObject, Protocol1, Protocol2 {
//body
}
協(xié)議中添加屬性
- 協(xié)議中可以添加屬性票摇,但是需要注意一下幾點(diǎn):
- 1、協(xié)議同時(shí)要求一個(gè)
屬性必須
明確是可讀的/可讀可寫的
- 屬性要求定義為
變量屬性
,即使用var
而不是let
- 1、協(xié)議同時(shí)要求一個(gè)
protocol CJLTest {
var age: Int {get set}
}
協(xié)議中定義方法
- 在協(xié)議中定義方法砚蓬,只需要定義當(dāng)前方法的名稱矢门、參數(shù)列表和返回值
- 在具體的類中遵守協(xié)議,并實(shí)現(xiàn)協(xié)議中的方法
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()
- 協(xié)議中也可以
定義初始化方法
灰蛙,當(dāng)實(shí)現(xiàn)初始化器時(shí)祟剔,必須使用required
關(guān)鍵字
protocol MyProtocol {
init(age: Int)
}
class CJLTeacher: MyProtocol{
var age: Int
required init(age: Int) {
self.age = age
}
}
- 如果一個(gè)協(xié)議只能被
類
實(shí)現(xiàn),需要協(xié)議繼承自AnyObject
摩梧。如果此時(shí)結(jié)構(gòu)體
遵守該協(xié)議峡扩,會(huì)報(bào)錯(cuò)
協(xié)議進(jìn)階 - 將協(xié)議作為類型
協(xié)議除了上述的基本用法,還有以下幾種用法:
1障本、作為
函數(shù)教届、方法或者初始化
程序中的參數(shù)類型或者返回值
2、作為
常量驾霜、變量或?qū)傩缘念愋?/code>
3案训、作為
數(shù)組、字典或
者其他容器
中項(xiàng)目的類型
通過(guò)繼承基類實(shí)現(xiàn)
下面一段代碼的打印結(jié)果是什么粪糙?(通過(guò)繼承基類
實(shí)現(xiàn))
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)
}
<!--打印結(jié)果-->
314.0
200.0
對(duì)于數(shù)組來(lái)說(shuō)强霎,當(dāng)前的大小是固定的,因?yàn)楫?dāng)前存放的都是引用類型
(即占8
字節(jié))蓉冈,其存儲(chǔ)結(jié)構(gòu)如下所示
通過(guò)協(xié)議實(shí)現(xiàn)
- 上述代碼的實(shí)現(xiàn)是通過(guò)繼承基類城舞,即
基類中的area必須有一個(gè)默認(rèn)實(shí)現(xiàn)
轩触,也可以通過(guò)協(xié)議
來(lái)替代當(dāng)前代碼的書寫方式
//2-2、通過(guò)協(xié)議實(shí)現(xiàn):area必須有一個(gè)默認(rèn)實(shí)現(xiàn)
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)
}
<!--打印結(jié)果-->
314.0
200.0
當(dāng)數(shù)組中的元素指定的Shape是類時(shí)家夺,數(shù)組中存儲(chǔ)的都是引用類型的地址脱柱,那么問(wèn)題來(lái)了,如果數(shù)組指定的Shape是一個(gè)協(xié)議時(shí)拉馋,數(shù)組中存儲(chǔ)的是什么榨为?
- 如果Shape協(xié)議提供了一個(gè)默認(rèn)實(shí)現(xiàn),此時(shí)的打印是什么煌茴?
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)
<!--打印結(jié)果-->
0.0
打印0.0
的原因是因?yàn)?code>在Extension中聲明的方法是靜態(tài)調(diào)用随闺,即在編譯鏈接后當(dāng)前底阿媽的地址就已經(jīng)確定了,我們是無(wú)法重寫
的蔓腐。這個(gè)可以通過(guò)SIL代碼來(lái)驗(yàn)證
協(xié)議示例代碼分析
下面通過(guò)一個(gè)簡(jiǎn)單的代碼來(lái)分析SIL
- 【示例1】:下面代碼的打印結(jié)果是什么矩乐?
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()
<!--打印結(jié)果-->
MyClass
MyClass
打印一樣的原因是因?yàn)樵?code>MyProtocol協(xié)議中有teach方法的聲明
-
查看
SIL
中兩種方式的調(diào)用有什么不同?定義為
MyProtocol
類型的對(duì)象object
回论,方法teach的調(diào)用在底層是通過(guò)witness_method
調(diào)用散罕,即通過(guò)PWT(協(xié)議目錄表)獲取對(duì)應(yīng)的函數(shù)地址
,其內(nèi)部也是通過(guò)類的函數(shù)表查找進(jìn)行調(diào)用
-
定義為
MyClass
類型的對(duì)象object1
透葛,方法teach的調(diào)用在底層是通過(guò)類的函數(shù)表
來(lái)查找函數(shù),主要是基于類的實(shí)際類型
其中卿樱,協(xié)議目錄表和函數(shù)表如下所示
查看協(xié)議中teach
方法具體實(shí)現(xiàn)的SIL代碼僚害,在內(nèi)部調(diào)用
的是MyClass類的函數(shù)表中的teach方法
【示例2】:如果去掉
MyProtocol
協(xié)議中teach方法的聲明,打印結(jié)果是什么繁调?
//如果去掉協(xié)議中的聲明呢萨蚕?打印結(jié)果是什么
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()
<!--打印結(jié)果-->
MyProtocol
MyClass
打印不一致的根本原因是MyProtocol
協(xié)議擴(kuò)展中實(shí)現(xiàn)的teach方法不能被類重寫,相當(dāng)于這是兩個(gè)方法蹄胰,并不是同一個(gè)
- 查看底層的SIL代碼
- 第一個(gè)打印
MyProtocol
岳遥,是因?yàn)檎{(diào)用的是協(xié)議擴(kuò)展中的teach方法,這個(gè)方法的地址是在編譯時(shí)期就已經(jīng)確定的裕寨,即通過(guò)靜態(tài)函數(shù)地址調(diào)度
- 第二個(gè)打印
MyClass
浩蓉,同上個(gè)例子一樣,是類的函數(shù)表調(diào)用
查看SIL中的witness_table
宾袜,其中已經(jīng)沒(méi)有teach方法
- 聲明在
Protocol
中的方法捻艳,在底層會(huì)存儲(chǔ)在PWT
,PWT中的方法也是通過(guò)class_method
庆猫,去類的V-Table
中找到對(duì)應(yīng)的方法的調(diào)度认轨。 - 如果沒(méi)有聲明在
Protocol
中的函數(shù),只是通過(guò)Extension
提供了一個(gè)默認(rèn)實(shí)現(xiàn)月培,其函數(shù)地址在編譯過(guò)程中就已經(jīng)確定了嘁字,對(duì)于遵守協(xié)議的類來(lái)說(shuō)恩急,這種方法是無(wú)法重寫的
- 第一個(gè)打印
協(xié)議的PWT存儲(chǔ)位置
我們?cè)诜治龊瘮?shù)調(diào)度時(shí),已經(jīng)知道了V-Table
是存儲(chǔ)在metadata
中的纪蜒,那么協(xié)議的PWT存儲(chǔ)在哪里呢衷恭?
- 下面代碼的打印結(jié)果是什么?
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))
<!--打印結(jié)果-->
40
40
8
8
-
首先通過(guò)
lldb
調(diào)試如下
-
查看對(duì)應(yīng)的SIL代碼霍掺,比往常的代碼多了一步
init_existential_addr
匾荆,可以理解為:使用了包含Circle
的existential container
來(lái)初始化circle
引用的內(nèi)存。通俗來(lái)說(shuō)就是將circle
包裝了存入existential container
初始化的內(nèi)存
其中杆烁,SIL官方文檔對(duì)init_existential_addr
的解釋如下
其中的existential container
是編譯器生成的一種特殊的數(shù)據(jù)類型牙丽,也用于管理遵守了相同協(xié)議的協(xié)議類型。因?yàn)檫@些數(shù)據(jù)類型的內(nèi)存空間尺寸不同兔魂,使用existential container
進(jìn)行管理可以實(shí)現(xiàn)存儲(chǔ)一致性 通過(guò)
IR
代碼烤芦,分析如下
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
; s4main6CircleCMa 等價(jià)于 type metadata accessor for main.Circle
%3 = call swiftcc %swift.metadata_response @"$s4main6CircleCMa"(i64 0) #7
%4 = extractvalue %swift.metadata_response %3, 0
; s4main6CircleCyACSdcfC 等價(jià)于 main.Circle.__allocating_init(Swift.Double) -> main.Circle
%5 = call swiftcc %T4main6CircleC* @"$s4main6CircleCyACSdcfC"(double 1.000000e+01, %swift.type* swiftself %4)
; 往一個(gè)內(nèi)存中存儲(chǔ)
; i32 0, i32 1 結(jié)構(gòu)體不偏移,并選擇第二個(gè)字段析校,相當(dāng)于將metadata放入 T4main5ShapeP結(jié)構(gòu)體的%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 等價(jià)于 protocol witness table for main.Circle : main.Shape in main 協(xié)議目錄表构罗,將其放入了 T4main5ShapeP 結(jié)構(gòu)體的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 等價(jià)于 main.circle : main.Shape, 將%5放入了 %T4main6CircleC** 中智玻,即 type <{ %swift.refcounted, %TSd }>遂唧,相當(dāng)于將HeapObject放入T4main6CircleC中 ==> type { HeapObject, metadata, PWT }
; 將 %T4main6CircleC* %5 實(shí)例對(duì)象地址 放入了 %T4main6CircleC** 二級(jí)指針里,也就意味著實(shí)例對(duì)象占用8字節(jié)吊奢,所以放入結(jié)構(gòu)體中就是占用8字節(jié)的大小
store %T4main6CircleC* %5, %T4main6CircleC** bitcast (%T4main5ShapeP* @"$s4main6circleAA5Shape_pvp" to %T4main6CircleC**), align 8
.....
仿寫結(jié)構(gòu)
然后通過(guò)上述的分析盖彭,仿寫整個(gè)內(nèi)部結(jié)構(gòu)
<!--1、仿寫整個(gè)結(jié)構(gòu)-->
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
//24 * i8 :因?yàn)槭?字節(jié)讀取页滚,所以寫成3個(gè)指針
var value1: UnsafeRawPointer
var value2: UnsafeRawPointer
var value3: UnsafeRawPointer
//type 存放metadata召边,目的是為了找到Value Witness Table 值目錄表
var type: UnsafeRawPointer
// i8* 存放pwt
var pwt: UnsafeRawPointer
}
<!--2、定義協(xié)議+類-->
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
}
}
}
//對(duì)象類型為協(xié)議
var circle: Shape = Circle(10.0)
<!--3裹驰、將circle強(qiáng)轉(zhuǎn)為protocolData結(jié)構(gòu)體-->
withUnsafePointer(to: &circle) { ptr in
ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
print(pointer.pointee)
}
}
<!--4隧熙、打印結(jié)果-->
protocolData(value1: 0x0000000100550100, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x0000000100008180, pwt: 0x0000000100004028)
lldb調(diào)試如下,其中value1
是HeapObject
幻林,type
是metadata
而
0x0000000100004028
可以通過(guò)nm + xcrun
來(lái)驗(yàn)證確實(shí)是 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
}
}
}
//對(duì)象類型為協(xié)議
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 :因?yàn)槭?字節(jié)讀取沪饺,所以寫成3個(gè)指針
var value1: UnsafeRawPointer
var value2: UnsafeRawPointer
var value3: UnsafeRawPointer
//type 存放metadata邻悬,目的是為了找到Value Witness Table 值目錄表
var type: UnsafeRawPointer
// i8* 存放pwt
var pwt: UnsafeRawPointer
}
//將circle強(qiáng)轉(zhuǎn)為protocolData結(jié)構(gòu)體
withUnsafePointer(to: &rectangle) { ptr in
ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
print(pointer.pointee)
}
}
<!--打印結(jié)果-->
protocolData(value1: 0x4024000000000000, value2: 0x4034000000000000, value3: 0x0000000000000000, type: 0x0000000100004098, pwt: 0x0000000100004028)
針對(duì)打印結(jié)果的lldb調(diào)試如下,value1
存儲(chǔ)10
随闽,value2
存儲(chǔ)20
- 查看其IR代碼
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
; 占用16字節(jié)
%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 }>*
; 第一個(gè)索引:i32 0 表示需要跨越全局變量 父丰,其實(shí)就是 <{ i8**, i64, <{ i32, i32, i32, i32, i32, i32, i32 }>的首地址
; 第二個(gè)索引:i32 1 選擇結(jié)構(gòu)體的第二個(gè)字段
; 存儲(chǔ)到結(jié)構(gòu)體的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 結(jié)構(gòu)體來(lái)存儲(chǔ)
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值放入內(nèi)存中,有偏移蛾扇,%4 攘烛、%5分別的偏移是0、1镀首,是針對(duì) T4main5ShapeP 結(jié)構(gòu)體的偏移
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個(gè)屬性呢坟漱?
- 如果struct的結(jié)構(gòu)體屬性是3個(gè)呢
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
}
}
}
<!--打印結(jié)果-->
protocolData(value1: 0x4024000000000000, value2: 0x4034000000000000, value3: 0x403e000000000000, type: 0x0000000100004098, pwt: 0x0000000100004028)
從結(jié)果中可以看出,是存儲(chǔ)在value3
如果struct中有4個(gè)屬性呢更哄?
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
}
}
}
<!--打印結(jié)果-->
protocolData(value1: 0x0000000100546a50, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x00000001000040c0, pwt: 0x0000000100004050)
其中value1
是一個(gè)堆區(qū)地址
芋齿,堆區(qū)地址中存儲(chǔ)了4個(gè)屬性的值
協(xié)議底層存儲(chǔ)結(jié)構(gòu)總結(jié)
所以針對(duì)協(xié)議,其底層的存儲(chǔ)結(jié)構(gòu)如圖所示:
1成翩、
前24
個(gè)字節(jié)觅捆,主要用于存儲(chǔ)遵循了協(xié)議的class/struct的屬性值
,如果24字節(jié)不夠存儲(chǔ)麻敌,會(huì)在堆區(qū)開辟一個(gè)內(nèi)存空間用于存儲(chǔ)栅炒,24字節(jié)中的前8個(gè)字節(jié)存儲(chǔ)堆區(qū)地址(如果超出24,是直接分配堆區(qū)空間术羔,然后存儲(chǔ)值
赢赊,并不是先存儲(chǔ)值,然后發(fā)現(xiàn)不夠再分配堆區(qū)空間)2级历、
后16
個(gè)字節(jié)分別用于存儲(chǔ)vwt
(值目錄表)释移、pwt
(協(xié)議目錄表)
繼續(xù)分析
回到下面這個(gè)例子中,其中for-in循環(huán)
能區(qū)分不同的area的原因主要是因?yàn)?protocol
的pwt
寥殖,pwt
其內(nèi)部也是通過(guò)class_method
查找玩讳,同時(shí)在運(yùn)行過(guò)程中存儲(chǔ)了metadata
,所以可以根據(jù)metadata找到對(duì)應(yīng)的v-table扛禽,從而完成方法的調(diào)用
//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)
//所謂的多態(tài):根據(jù)具體的類來(lái)決定調(diào)度的方法
var shapes: [Shape] = [circle, rectangle]
//這里能區(qū)分不同area的原因是因?yàn)?在protocol中存放了pwt(協(xié)議目錄表)皱坛,可以根據(jù)這個(gè)表來(lái)正確調(diào)用對(duì)應(yīng)的實(shí)現(xiàn)方法(pwt中也是通過(guò)class_method查找编曼,同時(shí)在運(yùn)行過(guò)程中也記錄了metadata,在pwt中通過(guò)metadata查找V-Table剩辟,從而完成當(dāng)前方法的調(diào)用)
for shape in shapes{
print(shape.area)
}
- 繼續(xù)回到
struct
的例子掐场,將其賦值給另一個(gè)變量,其內(nèi)存存放的是否是一樣的贩猎?
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
}
}
}
//對(duì)象類型為協(xié)議
var rectangle: Shape = Rectangle(10.0, 20.0)
//將其賦值給另一個(gè)協(xié)議變量
var rectangle1: Shape = rectangle
<!--查看其內(nèi)存地址-->
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
//24 * i8 :因?yàn)槭?字節(jié)讀取熊户,所以寫成3個(gè)指針
var value1: UnsafeRawPointer
var value2: UnsafeRawPointer
var value3: UnsafeRawPointer
//type 存放metadata,目的是為了找到Value Witness Table 值目錄表
var type: UnsafeRawPointer
// i8* 存放pwt
var pwt: UnsafeRawPointer
}
//將circle強(qiáng)轉(zhuǎn)為protocolData結(jié)構(gòu)體
withUnsafePointer(to: &rectangle) { ptr in
ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
print(pointer.pointee)
}
}
打印結(jié)果如下吭服,兩個(gè)協(xié)議變量內(nèi)存存放的東西是一樣
的
- 如果
修改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
}
}
}
//對(duì)象類型為協(xié)議
var rectangle: Shape = Rectangle(10.0, 20.0)
//將其賦值給另一個(gè)協(xié)議變量
var rectangle1: Shape = rectangle
//查看其內(nèi)存結(jié)構(gòu)體
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
//24 * i8 :因?yàn)槭?字節(jié)讀取,所以寫成3個(gè)指針
var value1: UnsafeRawPointer
var value2: UnsafeRawPointer
var value3: UnsafeRawPointer
//type 存放metadata,目的是為了找到Value Witness Table 值目錄表
var type: UnsafeRawPointer
// i8* 存放pwt
var pwt: UnsafeRawPointer
}
//將circle強(qiáng)轉(zhuǎn)為protocolData結(jié)構(gòu)體
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
通過(guò)lldb調(diào)試發(fā)現(xiàn)蝌戒,在rectangle1
變量修改width
之后串塑,其存儲(chǔ)數(shù)據(jù)的堆區(qū)地址發(fā)生了變化。這就是所謂的寫時(shí)復(fù)制
【當(dāng)復(fù)制時(shí)北苟,并沒(méi)有值的修改桩匪,所以兩個(gè)變量指向同一個(gè)堆區(qū)內(nèi)存,當(dāng)?shù)诙€(gè)變量修改了屬性值時(shí)友鼻,會(huì)將原本堆區(qū)內(nèi)存的值拷貝到一個(gè)新的堆區(qū)內(nèi)存傻昙,并進(jìn)行值的修改】
疑問(wèn)1:如果將struct修改為class,是否也是寫時(shí)復(fù)制彩扔?
如果上述例子中妆档,遵循協(xié)議的是類(即struct 改成 class),是否也是寫時(shí)復(fù)制
呢借杰?
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調(diào)試結(jié)果如下过吻,屬性值修改前后,堆區(qū)地址并沒(méi)有變化
蔗衡,符合對(duì)值類型和引用類型的理解
值類型
在 傳遞過(guò)程中 并不共享狀態(tài)
-
引用類型
在 傳遞過(guò)程中共享狀態(tài)
問(wèn)題:如果超過(guò)24字節(jié)纤虽,是先存儲(chǔ)到value1后發(fā)現(xiàn)不夠再分配堆區(qū),還是直接分配绞惦?
如下所示逼纸,struct中定義4個(gè)屬性
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代碼,從代碼中可以看出济蝉,是
先分配堆區(qū)空間杰刽,再將屬性值存儲(chǔ)到堆區(qū)空間中
疑問(wèn)3:如果是存儲(chǔ)的值類型是String呢?
如下所示王滤,存儲(chǔ)的值類型是String類型贺嫂,查看其底層存儲(chǔ)情況
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")
//查看其內(nèi)存結(jié)構(gòu)體
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
struct protocolData {
//24 * i8 :因?yàn)槭?字節(jié)讀取,所以寫成3個(gè)指針
var value1: UnsafeRawPointer
var value2: UnsafeRawPointer
var value3: UnsafeRawPointer
//type 存放metadata雁乡,目的是為了找到Value Witness Table 值目錄表
var type: UnsafeRawPointer
// i8* 存放pwt
var pwt: UnsafeRawPointer
}
//將circle強(qiáng)轉(zhuǎn)為protocolData結(jié)構(gòu)體
withUnsafePointer(to: &rectangle) { ptr in
ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
print(pointer.pointee)
}
}
-
查看其IR代碼
- lldb調(diào)試如下第喳,底層也是
通過(guò)value存儲(chǔ)
總結(jié)
協(xié)議在底層的存儲(chǔ)結(jié)構(gòu)體如下:
前面的24字節(jié),官方稱為
Value Buffer
踱稍,用來(lái)存儲(chǔ)當(dāng)前的值-
如果超過(guò)
Value Buffer
最大容量(24字節(jié))值類型
采用copy-write
曲饱,即拷貝時(shí)拷貝content整體,當(dāng)修改值時(shí)珠月,會(huì)先檢查引用計(jì)數(shù)扩淀,如果引用計(jì)數(shù)大于1,會(huì)開辟新的堆的內(nèi)存空間
啤挎,然后將修改的值放入新的空間中驻谆,其目的是為了提高內(nèi)存的利用率,降低堆區(qū)的內(nèi)存消耗,從而實(shí)現(xiàn)性能的提升
引用類型
則是使用同一個(gè)堆區(qū)地址
胜臊,因?yàn)槠淇截愖兞颗c原變量是共享狀態(tài)
總結(jié)
-
class氛谜、struct、enum都可以遵守協(xié)議区端,有以下幾點(diǎn)說(shuō)明:
1值漫、多個(gè)協(xié)議之間需要使
用逗號(hào)分隔
2、如果class中有
superClass
织盼,一般放在協(xié)議之前
-
協(xié)議中可以添加
屬性
杨何,有以下兩點(diǎn)說(shuō)明:1、屬性必須明確是
可讀(get)/可讀可寫(get + set)
的2沥邻、屬性使用
var
修飾
協(xié)議中可以
定義方法
危虱,只需要定義當(dāng)前方法的名稱+參數(shù)列表+返回值
,其具體實(shí)現(xiàn)可以通過(guò)協(xié)議的extension實(shí)現(xiàn)
唐全,或者在遵守協(xié)議時(shí)實(shí)現(xiàn)
協(xié)議中也可以
定義初始化方法
埃跷,當(dāng)實(shí)現(xiàn)初始化器時(shí),必須使用required
關(guān)鍵字如果協(xié)議只能被class實(shí)現(xiàn)邮利,需要協(xié)議繼承自
AnyObject
-
協(xié)議也可以作為類型弥雹,有以下三種場(chǎng)景:
1、作為
函數(shù)延届、方法或者初始化
程序中的參數(shù)類型或者返回值
2剪勿、作為
常量、變量或?qū)傩缘念愋?/code>
3方庭、作為
數(shù)組厕吉、字典或
者其他容器
中項(xiàng)目的類型
-
協(xié)議的底層存儲(chǔ)結(jié)構(gòu):
24字節(jié)valueBuffer + vwt(8字節(jié)) + pwt(8字節(jié))
1、
前24
個(gè)字節(jié)械念,官方稱為Value Buffer
头朱,主要用于存儲(chǔ)遵循了協(xié)議的class/struct的屬性值
-
2、如果超過(guò)
Value Buffer
最大容量(1)
值類型
采用copy-write
(2)
引用類型
則是使用同一個(gè)堆區(qū)地址
3龄减、
后16
個(gè)字節(jié)分別用于存儲(chǔ)vwt
(值目錄表)项钮、pwt
(協(xié)議目錄表)