Swift-進(jìn)階 13:協(xié)議Protocol

Swift 進(jìn)階之路 文章匯總

本文主要分析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
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ú)法重寫的

協(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匾荆,可以理解為:使用了包含Circleexistential 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)試如下,其中value1HeapObject幻林,typemetadata


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)?protocolpwt寥殖,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é)議目錄表)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市欺殿,隨后出現(xiàn)的幾起案子寄纵,更是在濱河造成了極大的恐慌鳖敷,老刑警劉巖脖苏,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異定踱,居然都是意外死亡棍潘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)亦歉,“玉大人恤浪,你說(shuō)我怎么就攤上這事‰瓤” “怎么了水由?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赛蔫。 經(jīng)常有香客問(wèn)我砂客,道長(zhǎng),這世上最難降的妖魔是什么呵恢? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任鞠值,我火速辦了婚禮,結(jié)果婚禮上渗钉,老公的妹妹穿的比我還像新娘彤恶。我一直安慰自己,他們只是感情好鳄橘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布声离。 她就那樣靜靜地躺著,像睡著了一般瘫怜。 火紅的嫁衣襯著肌膚如雪抵恋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天宝磨,我揣著相機(jī)與錄音弧关,去河邊找鬼。 笑死唤锉,一個(gè)胖子當(dāng)著我的面吹牛世囊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窿祥,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼株憾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了晒衩?” 一聲冷哼從身側(cè)響起嗤瞎,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎听系,沒(méi)想到半個(gè)月后贝奇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡靠胜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年掉瞳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了毕源。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陕习,死狀恐怖霎褐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情该镣,我是刑警寧澤冻璃,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站损合,受9級(jí)特大地震影響俱饿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塌忽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一拍埠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧土居,春花似錦枣购、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至眷蜓,卻和暖如春分瘾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吁系。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工德召, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汽纤。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓上岗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蕴坪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肴掷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容