溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解Swift中的協(xié)議

發(fā)布時間:2021-10-14 09:23:04 來源:億速云 閱讀:145 作者:iii 欄目:web開發(fā)

本篇內(nèi)容主要講解“如何理解Swift中的協(xié)議”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“如何理解Swift中的協(xié)議”吧!

1. 前言

協(xié)議定義了一個藍(lán)圖,規(guī)定了用來實現(xiàn)某一特定任務(wù)或者功能的方法、屬性,以及其他需要的東西。類、結(jié)構(gòu)體和枚舉都可以遵循協(xié)議,并為協(xié)議定義的這些要求提供具體實現(xiàn)。某個類型能夠滿足某個協(xié)議的要求,就可以說該類型遵循這個協(xié)議。

除了遵循協(xié)議的類型必須實現(xiàn)的要求外,還可以對協(xié)議進行擴展,通過擴展來實現(xiàn)一部分要求或者實現(xiàn)一些附加功能,這些遵循協(xié)議的類型就能夠使用這些功能。

2. 協(xié)議的基本用法

? 2.1 協(xié)議語法

協(xié)議的定義方式與類、結(jié)構(gòu)體和枚舉的定義非常相似

1、基本語法

protocol SomeProtocol {     // 這里是協(xié)議的定義部分 }

2、如果讓自定義的類型遵循某個協(xié)議,在定義類型時,需要在類型名稱后面加上協(xié)議名稱,中間以冒號(:)隔開,如果需要遵循多個協(xié)議時,個協(xié)議之間用逗號(,)分割:

struct SomeStructure: FirstProtocol, AnotherProtocol {     // 這里是結(jié)構(gòu)體的定義部分 }

3、如果自定義類型擁有一個父類,應(yīng)該將父類名放在遵循協(xié)議名之前,以逗號分隔:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {     // 這里是類的定義部分 }

? 2.2 屬性要求

我們可以在協(xié)議中添加屬性,但需要注意以下幾點:

  1. 屬性可以是實例屬性和類型屬性

  2. 屬性需要使用 var 修飾,不能屬于 let

  3. 類型屬性只能使用 static 修飾,不能使用 class

  4. 我們需要聲明屬性必須是可讀的或者可讀可寫的

protocol SomeProtocol {     var propertyOne: Int { get set }     var propertyTwo: Int { get }     static var propertyThree: Int { get set } }

? 2.3 方法要求

我們可以在協(xié)議中添加方法,但需要注意以下幾點:

  1. 可以是實例方法或類方法

  2. 像普通方法一樣放在協(xié)議定義中,但不需要大括號和方法體

  3. 協(xié)議中不支持為協(xié)議中的方法提供默認(rèn)參數(shù)

  4. 協(xié)議中的類方法也只能使用 static 關(guān)鍵字作為前綴,不能使用 class

  5. 可以使用 mutating 提供異變方法,以使用該方法時修改實體的屬性等

  6. 可以定義構(gòu)造方法,但是使用的時候需要使用 required 關(guān)鍵字

protocol SomeProtocol {     func someMethod1()     func someMethod2() ->Int }

構(gòu)造方法

protocol SomeProtocol {     init(param: Int) }  class SomeClass: SomeProtocol {     required init(param: Int) { } }

異變方法

protocol Togglable {     mutating func toggle() }  enum OnOffSwitch: Togglable {     case off, on     mutating func toggle() {         switch self {         case .off:             self = .on         case .on:             self = .off         }     } }

? 2.4 協(xié)議作為類型

盡管協(xié)議本身并未實現(xiàn)任何功能,但是協(xié)議可以被當(dāng)做一個功能完備的類型來使用。協(xié)議作為類型使用,有時被稱作「存在類型」,這個名詞來著存在著一個類型T,該類型遵循協(xié)議T。

協(xié)議可以像其他普通類型一樣使用,使用場景如下:

  • 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型

  • 作為常量、變量或?qū)傩缘念愋?/p>

  • 作為數(shù)組、字典或其他容器中的元素類型

protocol SomeProtocol { }  class SomeClass {     required init(param: SomeProtocol) {} }

? 2.5 其他

  • 協(xié)議還可以被繼承

  • 可以在擴展里面遵循協(xié)議

  • 在擴展里面聲明采納協(xié)議

  • 使用合成來采納協(xié)議

  • 可以定義由類專屬協(xié)議,只需要繼承自AnyObject

  • 協(xié)議可以合成

  • 協(xié)議也可以擴展

3. 協(xié)議中方法的調(diào)用

舉個例子,在數(shù)學(xué)中我們會求某個圖形的面積,但是不同形狀求面積的公式是不一樣的,如果用代碼來實現(xiàn)可以怎么來實現(xiàn)呢?

首先我們可以通過繼承父類的方法來實現(xiàn),但是在這里我們就可以使用協(xié)議來實現(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)  print(circle.area) print(rectangle.area)  <!--打印結(jié)果--> 314.0 200.0

此時的打印結(jié)果是符合我們的預(yù)期的。

我們知道協(xié)議可以擴展,此時我們把協(xié)議的代碼修改成如下:

protocol Shape { //    var area: Double {get} } extension Shape{     var area: Double {         get{return 0.0}     } }  <!--打印結(jié)果--> 0.0 0.0

此時并沒有如我們預(yù)期的打印,如果我們聲明變量的時候?qū)懗扇缦履兀?/p>

var circle: Circle = Circle.init(10.0) var rectangle: Rectangle = Rectangle.init(10.0, 20.0)  <!--打印結(jié)果--> 314.0 200.0

此時的打印就符合我們的預(yù)期了。

其實我們也能夠清楚的了解到為什么會打印 0.0,在 Swift 方法調(diào)度這篇文章中我們介紹了 extension  中聲明的方法是靜態(tài)調(diào)用的,也就是說在編譯后當(dāng)前代碼的地址已經(jīng)確定,我們無法修改,當(dāng)聲明為 Shap 類型后,默認(rèn)調(diào)用的就是 Shape extension  中的屬性的 get 方法。下面我們在通過sil代碼來驗證一下,關(guān)于生成 sil 代碼的方法,請參考我以前的文章。

為了方便查看,我們精簡并修改代碼為如下:

protocol Shape {//    var area: Double {get}}extension Shape{    var area: Double {        get{return 0.0}    }}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.init(10.0)var a = circle.area

生成的 sil 代碼:

如何理解Swift中的協(xié)議

通過 sil 代碼我們可以清晰的看到,這里直接調(diào)用的 Shape.area.getter 方法。

下面我們換一些簡單的代碼再次看一下:

protocol PersonProtocol {     func eat() } extension PersonProtocol{     func eat(){ print("PersonProtocol eat") } } class Person: PersonProtocol{     func eat(){ print("Person eat") } } let p: PersonProtocol = Person() p.eat() let p1: Person = Person() p1.eat()  <!--打印結(jié)果--> Person eat Person eat

可以看到上面這段代碼的打印結(jié)果都是 Person  eat,那么為什么會打印相同的結(jié)果呢?首先通過代碼我們可以知道,在PersonProtocol中聲明了eat方法。對于聲明的協(xié)議方法,如果類中也實現(xiàn)了,就不會調(diào)用協(xié)議擴展中的方法。上面的屬性的例子中并沒有在協(xié)議中聲明屬性,只是在協(xié)議擴展中添加了一個屬性。下面我們看看上面這段代碼的sil代碼:

如何理解Swift中的協(xié)議

首先我們可以看到,對于兩個 eat 方法的確實存在不同,首先聲明為協(xié)議類型的變量調(diào)用 eat 方法是通過 witness_method  調(diào)用,另一個則是通過 class_method調(diào)用。

  • witness_method是通過PWT(協(xié)議目擊表)獲取對應(yīng)的函數(shù)地址

  • class_method是通過類的函數(shù)表來查找函數(shù)進行調(diào)用

如何理解Swift中的協(xié)議

在剛剛 sil 代碼中我們可以找到 sil_witness_table,在里面有 PersonProtocol.eat方法,找到  PersonProtocol.eat 方法可以發(fā)現(xiàn)里面是調(diào)用 class_method 尋找的類中 VTable 的 Person.eat方法。

如果我們不在協(xié)議中聲明 eat 方法:

protocol PersonProtocol { //    func eat() } extension PersonProtocol{     func eat(){ print("PersonProtocol eat") } } class Person: PersonProtocol{     func eat(){ print("Person eat") } } let p: PersonProtocol = Person() p.eat() let p1: Person = Person() p1.eat()  <!--打印結(jié)果--> PersonProtocol eat Person eat

查看 sil 代碼:

如何理解Swift中的協(xié)議

此時我們可以看到,對于不在協(xié)議中聲明方法的時候,依然是直接調(diào)用(靜態(tài)調(diào)用)。

所以對于協(xié)議中方法的調(diào)度:

對于不在協(xié)議中聲明的方法

  • 在協(xié)議擴展中有實現(xiàn)就是直接調(diào)用

  • 在遵循協(xié)議的實體中按照其調(diào)度方式?jīng)Q定

  • 兩處都實現(xiàn)了,聲明的實例是協(xié)議類型則直接調(diào)用協(xié)議擴展中的方法,反之調(diào)用遵循協(xié)議實體中的方法

對于聲明在協(xié)議中的方法

  • 如果遵循該協(xié)議的實體實現(xiàn)了該方法,則通過PWT協(xié)議目擊表查找到實現(xiàn)的方法進行調(diào)用(與聲明變量的類型無關(guān))

  • 如果遵循協(xié)議的實體沒實現(xiàn),協(xié)議擴展實現(xiàn)了,則會調(diào)用協(xié)議擴展中的方法

4. 協(xié)議原理探索

在上面探索協(xié)議中的方法調(diào)用的時候,我們提到過 PWT 也就是 Protocol witness table,協(xié)議目擊表,那么它存儲在什么地方呢?我們在  Swift 方法調(diào)度這篇文章中講過,V-Table 是存儲在 metadata 中的,那么我們就探索一下 PWT 的存儲位置。

? 4.1 內(nèi)存占用

首先我們先來看看如下代碼的的打印結(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

? 4.2 lldb探索內(nèi)存結(jié)構(gòu)

看到這個打印結(jié)果我能第一時間想到的就是生命為協(xié)議類型會存儲更多的信息。生命為類的時候,存儲的是類的實例對象的指針 8 字節(jié)。下面我們通過 lldb  調(diào)試來探索一下這個 40 字節(jié)都存儲了什么信息。

如何理解Swift中的協(xié)議

? 4.3 sil 探索內(nèi)存結(jié)構(gòu)

通過 lldb 我們可以看到其內(nèi)部應(yīng)該存儲著一些信息,那么具體存了什么呢?我們在看看 sil 代碼:

如何理解Swift中的協(xié)議

在sil代碼中我們可以看到,在初始化 circle 這個變量的時候使用到了 init_existential_addr,查看SIL文檔:

如何理解Swift中的協(xié)議

譯文:用一個準(zhǔn)備好包含類型為 $T 的存在容器部分初始化 %0  引用的內(nèi)存。該指令的結(jié)果是一個地址,該地址引用了所包含值的存儲空間,該存儲空間仍然沒有初始化。包含的值必須存儲為 -d 或  copy_addr-ed,以便完全初始化存在值。如果存在容器的值未初始化時需要銷毀,則必須使用 deinit_existential_addr  來完成此操作??梢韵裢R粯邮褂?destroy_addr 銷毀完全初始化的存在性容器。銷毀一個部分初始化存在容器的addr是未定義的行為。

文檔中的意思是,使用了包含 $T 的 existential container 來初始化 %0 引用的內(nèi)存。在這里就是使用包含 Circle 的  existential container 來初始化 circle 引用的內(nèi)存,簡單來說就是將 circle 包裝到了一個 existential  container 初始化的內(nèi)存。

existential container  是編譯器生成的一種特殊的數(shù)據(jù)類型,也用于管理遵守了相同協(xié)議的協(xié)議類型。因為這些塑化劑類型的內(nèi)存空間尺寸不同,使用 existential container  進行管理可以實現(xiàn)存儲一致性。

? 4.4 IR代碼探索內(nèi)存結(jié)構(gòu)

那么這個 existential container 都包裝了什么呢?目前通過sil代碼是看不出來什么了,那么我們就看看 IR 代碼:

; 一個結(jié)構(gòu)體,占用24字節(jié)內(nèi)存的數(shù)組,wift.type指針, i8*指針 %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }  define i32 @main(i32 %0, i8** %1) #0 { entry:   %2 = bitcast i8** %1 to i8*   ; main.Circle 的 metadata   %3 = call swiftcc %swift.metadata_response @"type metadata accessor for main.Circle"(i64 0) #7   %4 = extractvalue %swift.metadata_response %3, 0   ;init放   %5 = call swiftcc %T4main6CircleC* @"main.Circle.__allocating_init(Swift.Double) -> main.Circle"(double 1.000000e+01, %swift.type* swiftself %4)   ; 存%4 也就是metadata,存到T4main5ShapeP結(jié)構(gòu)體中,這里存的位置是第二個位置   store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"main.circle : main.Shape", i32 0, i32 1), align 8   ; 存pwt 也就是協(xié)議目擊表,存到第三個位置   store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"protocol witness table for main.Circle : main.Shape in main", i32 0, i32 0), i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"main.circle : main.Shape", i32 0, i32 2), align 8   ; 存放%5到二級指針,%5是init出來的對象,所以這里也就是個HeapObject結(jié)構(gòu),也就是T4main6CircleC結(jié)構(gòu)體的第一個8字節(jié)內(nèi)存空間處   store %T4main6CircleC* %5, %T4main6CircleC** bitcast (%T4main5ShapeP* @"main.circle : main.Shape" to %T4main6CircleC**), align 8 }

從 IR 代碼中我們可以知道,這里面的存儲是一個結(jié)構(gòu)體,結(jié)構(gòu)體中主要分為三個方面:

  1. 一個連續(xù)的24字節(jié)空間

  2. 一個存放metadata的指針

  3. 存放pwt指針

? 4.5 仿寫

下面我們就來仿寫一下這個結(jié)構(gòu):

struct HeapObject {     var type: UnsafeRawPointer     var refCount1: UInt32     var refCount2: UInt32 }  struct protocolData {     //24 * i8 :因為是8字節(jié)讀取,所以寫成3個指針     var value1: UnsafeRawPointer     var value2: UnsafeRawPointer     var value3: UnsafeRawPointer     //type 存放metadata,目的是為了找到Value Witness Table 值目錄表     var type: UnsafeRawPointer     // i8* 存放pwt指針     var pwt: UnsafeRawPointer }

4.5.1 類遵循協(xié)議重綁定

進行內(nèi)存的重新綁定:

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)  // 將circle強轉(zhuǎn)為protocolData結(jié)構(gòu)體 withUnsafePointer(to: &circle) { ptr in     ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in         print(pointer.pointee)     } }  <!--打印結(jié)果--> protocolData(value1: 0x00000001006082b0, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x0000000100008180, pwt: 0x0000000100004028)

如何理解Swift中的協(xié)議

lldb

通過lldb查看:

我們也可以看到對應(yīng)HeapObject結(jié)構(gòu)

該結(jié)構(gòu)存儲的是Circle的實例變量

并且在這里面的metadata與protocolData里面的存儲的metadata的地址是一致的;

通過cat address命令查看pwt對應(yīng)的指針,可以看到這段內(nèi)存對應(yīng)的就是SwiftProtocol.Circle的protocol witness  table。

至此我們就清楚的找到你了PWT的存儲位置,PWT存在協(xié)議類型實例的內(nèi)存結(jié)構(gòu)中。

4.5.2 結(jié)構(gòu)體遵循協(xié)議重綁定

在上面這個例子中我們使用的是類,我們知道類是引用類型,如果換成結(jié)構(gòu)體呢?

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 }   struct protocolData {     //24 * i8 :因為是8字節(jié)讀取,所以寫成3個指針     var value1: UnsafeRawPointer     var value2: UnsafeRawPointer     var value3: UnsafeRawPointer     //type 存放metadata,目的是為了找到Value Witness Table 值目錄表     var type: UnsafeRawPointer     // i8* 存放pwt指針     var pwt: UnsafeRawPointer }  // 將circle強轉(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)

如何理解Swift中的協(xié)議

此時我們可以看到,此時并沒有存儲一個HeapObject結(jié)構(gòu)的指針,而是直接存儲Double類型的值,metadata和pwt沒有變。

在看下IR代碼:

define i32 @main(i32 %0, i8** %1) #0 { entry:   %2 = bitcast i8** %1 to i8*   %3 = call swiftcc { double, double } @"main.Rectangle.init(Swift.Double, Swift.Double) -> main.Rectangle"(double 1.000000e+01, double 2.000000e+01)   ; 10   %4 = extractvalue { double, double } %3, 0   ; 20   %5 = extractvalue { double, double } %3, 1   ;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 }>* @"full type metadata for main.Rectangle", i32 0, i32 1) to %swift.type*), %swift.type** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"main.rectangle : main.Shape", i32 0, i32 1), align 8   ;pwt   store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"protocol witness table for main.Rectangle : main.Shape in main", i32 0, i32 0), i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"main.rectangle : main.Shape", i32 0, i32 2), align 8   ;存%4 也就是10   store double %4, double* getelementptr inbounds (%T4main9RectangleV, %T4main9RectangleV* bitcast (%T4main5ShapeP* @"main.rectangle : main.Shape" to %T4main9RectangleV*), i32 0, i32 0, i32 0), align 8   ; 存%5 也就是20   store double %5, double* getelementptr inbounds (%T4main9RectangleV, %T4main9RectangleV* bitcast (%T4main5ShapeP* @"main.rectangle : main.Shape" to %T4main9RectangleV*), i32 0, i32 1, i32 0), align 8 }

通過IR代碼我們可以看到:

  • 對于metadata和pwt的存儲依舊

  • 然后存儲了兩個Double值,并沒有存儲HeapObject類型的指針

那么如果有3個屬性呢?

struct Rectangle: Shape{     var width, width2, height: Double     init(_ width: Double, _ width2: Double, _ height: Double) {         self.width = width         self.width2 = width2         self.height = height     }      var area: Double{         get{             return width * height         }     } }  <!--內(nèi)存綁定后的打印結(jié)果--> protocolData(value1: 0x4024000000000000, value2: 0x4034000000000000, value3: 0x403e000000000000, type: 0x0000000100004098, pwt: 0x0000000100004028)

這個三個Value的值分別是10,20,30

那如果是4個呢?

struct Rectangle: Shape{     var width, width2, height, height1: Double     init(_ width: Double, _ width2: Double, _ height: Double, _ height1: Double) {         self.width = width         self.width2 = width2         self.height = height         self.height1 = height1     }      var area: Double{         get{             return width * height         }     } }  var rectangle: Shape = Rectangle(10.0, 20.0, 30.0, 40.0)  <!--內(nèi)存綁定后的打印結(jié)果--> protocolData(value1: 0x0000000100715870, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x00000001000040c0, pwt: 0x0000000100004050)

此時并沒有直接看到Double值了,查看value1的內(nèi)存:

如何理解Swift中的協(xié)議

此時我們可以看到,這個內(nèi)存中存儲了 10,20,30,40 這四個值。

所以如果我們需要存儲的數(shù)據(jù)超過了 24 x i8*,也就是 24 字節(jié)時,就會開辟內(nèi)存空間進行存儲。這里只存儲指向新開辟內(nèi)存空間的指針。

這里的順序是,如果不夠存儲就直接開辟內(nèi)存空間,存儲值,記錄指針。而不是先存儲不夠了在開辟內(nèi)存空間。

我們都知道,結(jié)構(gòu)體是值類型,如果超過這 24  字節(jié)的存儲空間就會開辟內(nèi)存用來存儲結(jié)構(gòu)體中的值,如果此時發(fā)生拷貝會是神馬結(jié)構(gòu)呢?下面我們就來驗證一下:

結(jié)構(gòu)體拷貝:

protocol Shape {     var area: Double {get} } struct Rectangle: Shape{     var width, width2, height, height1: Double     init(_ width: Double, _ width2: Double, _ height: Double, _ height1: Double) {         self.width = width         self.width2 = width2         self.height = height         self.height1 = height1     }      var area: Double{         get{             return width * height         }     } }  var rectangle: Shape = Rectangle(10.0, 20.0, 30.0, 40.0) var rectangle1 = rectangle  struct HeapObject {     var type: UnsafeRawPointer     var refCount1: UInt32     var refCount2: UInt32 }   struct protocolData {     //24 * i8 :因為是8字節(jié)讀取,所以寫成3個指針     var value1: UnsafeRawPointer     var value2: UnsafeRawPointer     var value3: UnsafeRawPointer     //type 存放metadata,目的是為了找到Value Witness Table 值目錄表     var type: UnsafeRawPointer     // i8* 存放pwt指針     var pwt: UnsafeRawPointer }  // 內(nèi)存重綁定 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)     } }  <!--打印結(jié)果--> protocolData(value1: 0x000000010683bac0, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x00000001000040c0, pwt: 0x0000000100004050) protocolData(value1: 0x000000010683bac0, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x00000001000040c0, pwt: 0x0000000100004050)

此時我們看到打印結(jié)果是一樣的。

那么修改呢?

添加如下代碼:

protocol Shape {     // 為了方便修改,在這聲明一下     var width: Double {get set}     var area: Double {get} }  rectangle1.width = 50

如何理解Swift中的協(xié)議

通過 lldb  重新打印,我們可以看到在修改值后,內(nèi)存地址已經(jīng)修改了,此時就是寫時復(fù)制。當(dāng)復(fù)制時并沒有值的修改,所以兩個變量指向同一個堆區(qū)內(nèi)存。當(dāng)修改變量的時候,會原本的堆區(qū)內(nèi)存的值拷貝到一個新的內(nèi)存區(qū)域,并進行值的修改。

如果我們將 struct 修改成 class,這里并不會觸發(fā)寫時復(fù)制,因為在 Swift  中類是引用類型,修改類的值就是修改其引用地址中的值。這里就不驗證了,感興趣的可以自己去試試。

如果我們將 Double 換成 String 原理也是一致的,這里也就不一一驗證了。

4.5.3 小結(jié)

至此我們也就清楚了,為什么協(xié)議中通過 witness_method 調(diào)用,最終能找到 V-Table 中的方法,原因就是存儲了 metadata 和  pwt。這也是我們都聲明為協(xié)議類型,最終能打印出不同形狀的面積根本原因。

5. 總結(jié)

至此我們對Swift中協(xié)議的分析就結(jié)束了,現(xiàn)總結(jié)如下:

1.Swift中類、結(jié)構(gòu)體、枚舉都可以遵守協(xié)議

2.遵守多個協(xié)議使用逗號(,)分隔

3.有父類的,父類寫在前面,協(xié)議在后面用逗號(,)分隔

4.協(xié)議中可以添加屬性

  • 屬性可以是實例屬性和類型屬性

  • 屬性需要使用var修飾,不能屬于let

  • 類型屬性只能使用static修飾,不能使用class

  • 我們需要聲明屬性必須是可讀的或者可讀可寫的

5.協(xié)議中可以添加方法

  • 可以是實例方法或類方法

  • 像普通方法一樣放在協(xié)議定義中,但不需要大括號和方法體

  • 協(xié)議中不支持為協(xié)議中的方法提供默認(rèn)參數(shù)

  • 協(xié)議中的類方法也只能使用static關(guān)鍵字作為前綴,不能使用class

  • 可以使用mutating提供異變方法,以使用該方法時修改實體的屬性等。

  • 可以定義構(gòu)造方法,但是使用的時候需要使用required關(guān)鍵字

6.如果定義由類專屬協(xié)議,則需要繼承自AnyObject

7.協(xié)議可以作為類型

  • 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型

  • 作為常量、變量或?qū)傩缘念愋?/p>

  • 作為數(shù)組、字典或其他容器中的元素類型

8.協(xié)議的底層存儲結(jié)構(gòu)是:24字節(jié)的ValueBuffer+ metadata(8字節(jié),也就是vwt) + pwt(8字節(jié))

  • 前24字節(jié),官方說法是ValueBuffer,主要用于存儲遵循了協(xié)議的實體的屬性值

  • 如果超過ValueBuffer最大容量就會開辟內(nèi)存進行存儲,此24字節(jié)拿出8字節(jié)存儲指向該內(nèi)存區(qū)域的指針

  • 目前對于類,發(fā)現(xiàn)其存儲的都是指針

  • 存儲metadata是為了查找遵守協(xié)議的實體中實現(xiàn)協(xié)議的方法

  • pwt就是protocol witness table協(xié)議目擊表,存儲協(xié)議中的方法

到此,相信大家對“如何理解Swift中的協(xié)議”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI