一.協(xié)議與繼承
class LGTeacher{
var age = 10
var name = "Kody"
}
class Dog{
var name = "糯米"
var type = "白梗"
}
/*
這里有2個類,LGTeacher與Dog,此時想為2個類添加debug函數(shù)去打印類相關(guān)的信息
從繼承的角度上看,我們會封裝一個Animal類(公共的基類)。從業(yè)務(wù)邏輯來說,這么處理不太合適
*/
那么最直觀也是最簡單的辦法就是,給每一個類添加一個debug函數(shù)
class LGTeacher{
var age = 10
var name = "Kody"
func debug() {
print(...)
}
}
class Dog{
var name = "糯米"
var type = "白梗"
func debug() {
print(...)
}
}
如果我們對當(dāng)前代碼中的每個類都需要添加debug函數(shù),顯然上面這種方法是行不通的,于是有了下面的代碼
func debug(subject: Any){
print(.....)
}
當(dāng)然看到這里可能會覺得沒有問題,如果我們想要描述當(dāng)前類的具體信息,這個時候我們還需要引入一個公共的基類,同時我們還需要有一個公共的屬性description來讓子類重載,這無疑是對我們的代碼是很強(qiáng)的入侵。
所以這個時候我們通過協(xié)議來描述當(dāng)前類的具體行為,并通過extension的方式來對我們的類進(jìn)行擴(kuò)展,這無疑是最好的辦法
extension LGTeacher: CustomStringConvertible {
var description: String {
get {
return "LGTeacher: \(age)\(name)"
}
}
}
extension Dog: CustomStringConvertible {
var description: String {
get {
return "Dog: \(name)\(type)"
}
}
}
func debug(subject: CustomStringConvertible){
print(subject.description)
}
let t = LGTeacher()
let d = Dog()
debug(subject: t)
debug(subject: d)
看到這里我們就可以稍微的總結(jié)一下
-
Class本質(zhì)上定義了一個對象是什么 -
Protocol本質(zhì)上定義了一個對象有哪些行為
二.協(xié)議的基本語法
1.協(xié)議要求一個屬性必須明確是get或get和set
protocol MyProtocol {
//必須是var聲明的
var age: Int { get set}
//要求遵循協(xié)議的類/結(jié)構(gòu)體必須要實現(xiàn)get方法
var name: String { get }
}
//需要注意的是:并不是當(dāng)前聲明get的屬性一定是計算屬性
class LGTeacher: MyProtocol {
var age: Int
//此時的name并不是計算屬性
var name: String
init(_ age: Int,_ name: String) {
self.age = age
self.name = name
}
}
2.協(xié)議中的異變方法,表示在該方法可以改變其屬性的實例,以及該實例的所有屬性(用于枚舉和結(jié)構(gòu)體),在為類實現(xiàn)該方法的時候不需要寫mutating關(guān)鍵字
protocol MyProtocol {
mutating func test()
}
3.類在實現(xiàn)協(xié)議中的初始化器,必須使用required關(guān)鍵字修飾初始化器的實現(xiàn)(類的初始化器添加required修飾符來表明所有該類的子類如果要自定義初始化器就必須實現(xiàn)該初始化器)
關(guān)于required在類與結(jié)構(gòu)體中初始化器模塊有詳細(xì)講解
protocol MyProtocol {
init()
}
class LGPerson: MyProtocol {
required init() {}
}
//添加final關(guān)鍵字后,就不需要required。因為該類不允許被繼承,也就沒有了子類實現(xiàn)該初始化器的說法了
final class LGStudent: MyProtocol {
init() {}
}
4.類專用協(xié)議(通過添加AnyObject關(guān)鍵字到協(xié)議的繼承列表,就可以限制只能被類類型采納)
在Mirror源碼解析也講解到了AnyObject
protocol MyProtocol: AnyObject {}
5.可選協(xié)議:不想強(qiáng)制讓遵循協(xié)議的類類型實現(xiàn)
//定義一個可選協(xié)議一般有兩種方式
/*
方式1:使用@objc關(guān)鍵字,使用OC方式來使用optional聲明去可選協(xié)議
1.暴露給Objc運行時,依舊是函數(shù)表派發(fā)(如果是@objc + dynamic會改變?yōu)橄⑴砂l(fā)方式(objc_msgSend))
2.值類型不能使用該protocol,只能被class使用
*/
@objc protocol MyProtocol {
@objc optional func test()
}
class LGTeacher: MyProtocol {
// func test() {
// print("test")
// }
}
/*
方式2:使用extension來給出默認(rèn)實現(xiàn),來實現(xiàn)可選協(xié)議的功能
一般我們在Swift中會使用這種方式來實現(xiàn)可選協(xié)議
*/
protocol OptionalProtocol{
func method() //必須實現(xiàn)
func method1() //可選
func method2() //可選
}
extension OptionalProtocol {
func method1() {}
func method2() {}
}
這里總結(jié)一下@objc的使用
1.Selector中調(diào)用的方法需要在方法前聲明@objc,目的是允許這個函數(shù)在運行時通過 Objective-C 的消息機(jī)制調(diào)用
let btn = UIButton()
btn.addTarget(self, action: #selector(click), for: .touchUpInside)
@objc func click() {
print("clicked")
}
2.協(xié)議的方法可選時,協(xié)議和方法前面都要加上@objc
@objc protocol MyProtocol {
@objc optional func test()
}
3.用weak修飾協(xié)議時,協(xié)議前面要添加@objc
@objc protocol MyProtocol {
}
class LGTeacher {
weak var delegate: MyProtocol?
}
4.類前面加上@objcMembers,那么它及其子類、擴(kuò)展的方法都會隱式的加上@objc
@objcMembers
class LGTeacher {
}
如果此時不想在擴(kuò)展里加@objc,可以使用@nonobjc修飾
@objcMembers
class LGTeacher {
}
@nonobjc extension LGTeacher {
func test() {}
}
5.擴(kuò)展前加上@objc,那么里面的方法都會隱式加上@objc
class LGTeacher {
}
@objc extension LGTeacher {
func test() {}
}
6.函數(shù)前面加上@objc
class LGTeacher {
//加上@objc將該函數(shù)暴露給Runtime,依舊是函數(shù)表派發(fā)
@objc func test() {}
}
//加上@objc就可以使用Runtime相關(guān)API
let sel = #selector(LGTeacher.test)
let t = LGTeacher()
//當(dāng)然這里只有讓LGTeacher繼承自NSObject才能執(zhí)行這個sel
在類與結(jié)構(gòu)體(下)中函數(shù)派發(fā)方式模塊中也有對@objc的講解
二.協(xié)議原理探究
1.實例對象執(zhí)行協(xié)議函數(shù)
protocol MyProtocol {
func test()
}
class LGTeacher: MyProtocol {
//v-table
func test() {
print("test")
}
}
let t = LGTeacher()
t.test()
在類與結(jié)構(gòu)體(下)講解到Swift類函數(shù)派發(fā)方式為函數(shù)表調(diào)度,那么這里的test()是函數(shù)表的調(diào)度嗎?這里我們通過SIL來分析一下
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1tAA9LGTeacherCvp // id: %2
%3 = global_addr @$s4main1tAA9LGTeacherCvp : $*LGTeacher // users: %8, %7
%4 = metatype $@thick LGTeacher.Type // user: %6
// function_ref LGTeacher.__allocating_init()
%5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %7
store %6 to %3 : $*LGTeacher // id: %7
%8 = load %3 : $*LGTeacher // users: %9, %10
//這里的class_method為函數(shù)表調(diào)度方式
%9 = class_method %8 : $LGTeacher, #LGTeacher.test : (LGTeacher) -> () -> (), $@convention(method) (@guaranteed LGTeacher) -> () // user: %10
%10 = apply %9(%8) : $@convention(method) (@guaranteed LGTeacher) -> ()
%11 = integer_literal $Builtin.Int32, 0 // user: %12
%12 = struct $Int32 (%11 : $Builtin.Int32) // user: %13
return %12 : $Int32 // id: %13
} // end sil function 'main'
//test函數(shù)聲明在v-table當(dāng)中
sil_vtable LGTeacher {
#LGTeacher.test: (LGTeacher) -> () -> () : @$s4main9LGTeacherC4testyyF // LGTeacher.test()
#LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC // LGTeacher.__allocating_init()
#LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD // LGTeacher.__deallocating_deinit
}
sil_witness_table hidden LGTeacher: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance LGTeacher
}
class_method函數(shù)表調(diào)度
因此從SIL分析得出,遵循該協(xié)議的類實現(xiàn)該協(xié)議方法,通過實例對象調(diào)用協(xié)議方法,還是函數(shù)表的調(diào)度
2.協(xié)議類型實例執(zhí)行協(xié)議函數(shù)
此時我們把上面代碼中的let t = LGTeacher()改為let t: MyProtocol = LGTeacher()
//t的靜態(tài)類型:MyProtocol
//t的動態(tài)類型:LGTeacher
let t: MyProtocol = LGTeacher()
SIL代碼
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1tAA10MyProtocol_pvp // id: %2
%3 = global_addr @$s4main1tAA10MyProtocol_pvp : $*MyProtocol // users: %9, %7
%4 = metatype $@thick LGTeacher.Type // user: %6
// function_ref LGTeacher.__allocating_init()
%5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %8
%7 = init_existential_addr %3 : $*MyProtocol, $LGTeacher // user: %8
store %6 to %7 : $*LGTeacher // id: %8
%9 = open_existential_addr immutable_access %3 : $*MyProtocol to $*@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol // users: %11, %11, %10
//此時的調(diào)度方式變成了witness_method
%10 = witness_method $@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol, #MyProtocol.test : <Self where Self : MyProtocol> (Self) -> () -> (), %9 : $*@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9; user: %11
%11 = apply %10<@opened("11F6E340-93BB-11EC-8F99-501FC65B9E38") MyProtocol>(%9) : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9
%12 = integer_literal $Builtin.Int32, 0 // user: %13
%13 = struct $Int32 (%12 : $Builtin.Int32) // user: %14
return %13 : $Int32 // id: %14
} // end sil function 'main'
// test函數(shù)依舊聲明在v-table當(dāng)中
sil_vtable LGTeacher {
#LGTeacher.test: (LGTeacher) -> () -> () : @$s4main9LGTeacherC4testyyF // LGTeacher.test()
#LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC // LGTeacher.__allocating_init()
#LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD // LGTeacher.__deallocating_deinit
}
//此時多了一個witness_table。為每一個遵循協(xié)議的類記錄實現(xiàn)協(xié)議相關(guān)的編碼信息,也就是保存了協(xié)議函數(shù)的實現(xiàn)地址
sil_witness_table hidden LGTeacher: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance LGTeacher
}
witness_method會去查找這個類的witness-table(協(xié)議見證表)中找到方法的實現(xiàn)
關(guān)于此時witness_table中的s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW,也就是test()
// protocol witness for MyProtocol.test() in conformance LGTeacher
sil private [transparent] [thunk] @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW : $@convention(witness_method: MyProtocol) (@in_guaranteed LGTeacher) -> () {
// %0 // user: %1
bb0(%0 : $*LGTeacher):
%1 = load %0 : $*LGTeacher // users: %2, %3
%2 = class_method %1 : $LGTeacher, #LGTeacher.test : (LGTeacher) -> () -> (), $@convention(method) (@guaranteed LGTeacher) -> () // user: %3
%3 = apply %2(%1) : $@convention(method) (@guaranteed LGTeacher) -> ()
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function '$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW'
通過witness_table中的函數(shù)其實還是通過函數(shù)表調(diào)度方式調(diào)度LGTeacher.test
簡單總結(jié)一下:witness_table做了一層橋接,通過witness_table為每個實現(xiàn)協(xié)議的類記錄實現(xiàn)協(xié)議的編碼信息,找到具體的函數(shù)實現(xiàn),完成方法的調(diào)度。簡單的理解,協(xié)議見證表記錄錄的就是實現(xiàn)的協(xié)議函數(shù)具體信息
比如說,當(dāng)LGTeacher遵循了協(xié)議MyProtocol并實現(xiàn)了協(xié)議函數(shù)test(),那么通過協(xié)議類型調(diào)用函數(shù)test()時,就會創(chuàng)建一個witness_table,通過witness_table找到具體的函數(shù)實現(xiàn),完成函數(shù)的調(diào)度。簡單的來說就是利用witness_table做了一次橋接。
3.Arm64匯編分析witness_table
通過Arm64匯編代碼分析,在t.test()打上一個斷點

通過我們對匯編基礎(chǔ)的認(rèn)知,很明顯t.test()對于的匯編代碼為0x104f17a7c <+128>: blr x8。讀取的內(nèi)存x1加上0x8地址,存入x8
在blr x8行打上斷點,讀取寄存器x8的值
//發(fā)現(xiàn)x8就是LGTeacher協(xié)議見證表中對test函數(shù)的
(lldb) register read x8
x8 = 0x0000000104f17d04 projectTest`protocol witness for projectTest.MyProtocol.test() -> () in conformance projectTest.LGTeacher : projectTest.MyProtocol in projectTest at <compiler-generated>
進(jìn)入blr x8

此時,這里的blr x8才是真正的test函數(shù)實現(xiàn)
斷點打在blr x8,并讀取寄存器x8
(lldb) register read x8
x8 = 0x0000000104f17ab4 projectTest`projectTest.LGTeacher.test() -> () at main.swift:26
至此,通過匯編還原了witness_table的原理,也證實了通過SIL分析的邏輯。
問題1:兩個類繼承自同一協(xié)議會有一張還是兩張witness_table?
答案肯定是兩張,因為從witness_table為每個遵循協(xié)議的類記錄實現(xiàn)協(xié)議函數(shù)的相關(guān)信息。
問題2:以下代碼打印的值為什么?
1.extension添加默認(rèn)實現(xiàn)
protocol MyProtocol {
func test()
}
//為協(xié)議添加默認(rèn)實現(xiàn)
extension MyProtocol {
func test() {
print("MyProtocol")
}
}
class LGTeacher: MyProtocol {
//v-table
func test() {
print("LGTeacher")
}
}
//t的靜態(tài)類型:MyProtocol
//t的動態(tài)類型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()
當(dāng)然是LGTeacher,因為添加的默認(rèn)實現(xiàn)和通過witness_table找到函數(shù)的調(diào)用沒有任何的關(guān)系。執(zhí)行的邏輯是,通過witness_table找到在LGTeacher中對test的函數(shù),完成函數(shù)的調(diào)用
2.如果注釋掉func test()
protocol MyProtocol {
// func test()
}
//為協(xié)議添加默認(rèn)實現(xiàn)
extension MyProtocol {
func test() {
print("MyProtocol")
}
}
class LGTeacher: MyProtocol {
//v-table
func test() {
print("LGTeacher")
}
}
//t的靜態(tài)類型:MyProtocol
//t的動態(tài)類型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()
答案是MyProtocol,因為對于t來說靜態(tài)類型為MyProtocol,當(dāng)執(zhí)行到t.test()時,由于協(xié)議中沒有了test函數(shù),因此不會走協(xié)議見證表那套邏輯。而在extension MyProtocol中有test函數(shù)的實現(xiàn),因此直接靜態(tài)派發(fā)執(zhí)行test函數(shù)
對應(yīng)的的SIL
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
...
//直接執(zhí)行MyProtocol.test()
// function_ref MyProtocol.test()
%10 = function_ref @$s4main10MyProtocolPAAE4testyyF : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %11
...
} // end sil function 'main'
//此時的witness_table就為空了
sil_witness_table hidden LGTeacher: MyProtocol module main {
}
3.注釋掉LGTeacher中的test函數(shù)實現(xiàn)
protocol MyProtocol {
func test()
}
//為協(xié)議添加默認(rèn)實現(xiàn)
extension MyProtocol {
func test() {
print("MyProtocol")
}
}
class LGTeacher: MyProtocol {
//v-table
// func test() {
// print("LGTeacher")
// }
}
//t的靜態(tài)類型:MyProtocol
//t的動態(tài)類型:LGTeacher
let t: MyProtocol = LGTeacher()
t.test()
其實這個也很好理解,此時的witness_table中實現(xiàn)協(xié)議的信息存的是extension中的默認(rèn)實現(xiàn),因此肯定打印為MyProtocol。
對應(yīng)的SIL
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
...
%10 = witness_method $@opened("30ED99C2-93C7-11EC-A66A-501FC65B9E38") MyProtocol, #MyProtocol.test : <Self where Self : MyProtocol> (Self) -> () -> (), %9 : $*@opened("30ED99C2-93C7-11EC-A66A-501FC65B9E38") MyProtocol : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // type-defs: %9; user: %11
...
} // end sil function 'main'
// protocol witness for MyProtocol.test() in conformance LGTeacher
sil private [transparent] [thunk] @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW : $@convention(witness_method: MyProtocol) <τ_0_0 where τ_0_0 : LGTeacher> (@in_guaranteed τ_0_0) -> () {
// %0 // user: %2
bb0(%0 : $*τ_0_0):
//這里直接派發(fā)執(zhí)行MyProtocol.test()
// function_ref MyProtocol.test()
%1 = function_ref @$s4main10MyProtocolPAAE4testyyF : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> () // user: %2
%2 = apply %1<τ_0_0>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : MyProtocol> (@in_guaranteed τ_0_0) -> ()
%3 = tuple () // user: %4
return %3 : $() // id: %4
} // end sil function '$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW'
//此時vtable里沒有了test函數(shù)
sil_vtable LGTeacher {
#LGTeacher.init!allocator: (LGTeacher.Type) -> () -> LGTeacher : @$s4main9LGTeacherCACycfC // LGTeacher.__allocating_init()
#LGTeacher.deinit!deallocator: @$s4main9LGTeacherCfD // LGTeacher.__deallocating_deinit
}
//協(xié)議見證表里里有test函數(shù) ---> s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW
sil_witness_table hidden LGTeacher: MyProtocol module main {
method #MyProtocol.test: <Self where Self : MyProtocol> (Self) -> () -> () : @$s4main9LGTeacherCAA10MyProtocolA2aDP4testyyFTW // protocol witness for MyProtocol.test() in conformance LGTeacher
}
4.探究witness_table存放位置
我們在研究vTable的時候,得出vTable是存在TargetClassDescriptor中的
接下來來探究witness_table
protocol Shape {
var area: Double { get }
}
class Circle: Shape {
var radious: Double
init(_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return radious * radious * Double.pi
}
}
}
//1.靜態(tài)類型為Circle
var circle: Circle = Circle(10)
//這里很好理解,一個實例對象為16字節(jié)(metadata + refCount) + Double(8字節(jié))
print(class_getInstanceSize(Circle.self)) // 24
//這個8也很好理解,說白了就是circle變量指針的大小。占據(jù)8字節(jié)大小
print(MemoryLayout.size(ofValue: circle)) // 8
/*
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x0000000100010480
0 elements
0x0000000100010480 ---> 變量circle的地址
(lldb) x/8g 0x0000000100010480
0x100010480: 0x0000000100724c40 0x0000000000000000
0x100010490: 0x0000000000000000 0x0000000000000000
0x1000104a0: 0x0000000000000000 0x0000000000000000
0x1000104b0: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x0000000100724c40
0x100724c40: 0x00000001000103c8 0x0000000000000003
0x100724c50: 0x4024000000000000 0x0000000100724e40
0x100724c60: 0x00000009a0080001 0x00007ff843a06f00
0x100724c70: 0x0000000000000000 0x00007ff8422362d0
0x00000001000103c8 ---> metadata
0x0000000000000003 ---> refCount
0x4024000000000000 ---> Double值10
(lldb) cat address 0x0000000100724c40
address:0x0000000100724c40, (String) $R1 = "0x100724c40 heap pointer, (0x20 bytes), zone: 0x7ff84226d000"
浮點數(shù)的還原(lldb調(diào)試)
(lldb) expr -f float -- 0x4024000000000000
(Int) $R2 = 10
得出結(jié)論:var circle: Circle = Circle(10),存的是堆空間的內(nèi)存地址
*/
//2.靜態(tài)類型為Shape
var shape: Shape = Circle(10)
//type(of:)獲取的實際類型為Circle,因此大小肯定和Circle一樣也是24
print(class_getInstanceSize(type(of: shape) as? AnyClass)) // 24
/*
此時的數(shù)據(jù)類型占據(jù)的內(nèi)存大小為40,和上面的8完全就不一樣了。
但是我們可以得出一個結(jié)論,靜態(tài)類型不同,變量存儲的內(nèi)容是不一樣的。
猜想:肯定是多了witness_table的數(shù)據(jù)?
*/
print(MemoryLayout.size(ofValue: shape)) // 40
/*
(lldb) po withUnsafePointer(to: &shape) {print($0)}
0x0000000100010488
0 elements
(lldb) x/8g 0x0000000100010488
0x100010488: 0x000000010b311280 0x0000000000000000
0x100010498: 0x0000000000000000 0x00000001000103c8
0x1000104a8: 0x000000010000c2e8 0x0000000000000000
0x1000104b8: 0x0000000000000000 0x0000000000000000
前5個8字節(jié)存放的就是shape的內(nèi)容(40字節(jié))
分析第一個8字節(jié) ---> 0x000000010b311280(實例對象的堆空間地址)
(lldb) x/8g 0x000000010b311280
0x10b311280: 0x00000001000103c8 0x0000000000000003
0x10b311290: 0x4024000000000000 0x00007ff84223a498
0x10b3112a0: 0x0000000000000001 0x00007ff84223a8e0
0x10b3112b0: 0x0000000000000007 0x00007ff84223a240
第二、三個8字節(jié)都為0
分析第四個8字節(jié) ---> 0x00000001000103c8(實例對象的metadata,動態(tài)類型的metadata)
分析第五個8字節(jié) ---> 0x000000010000c2e8(witness table)
(lldb) cat address 0x000000010000c2e8
address:0x000000010000c2e8, 1c8protocol witness table for swiftTest.Circle : swiftTest.Shape in swiftTest <+0> , ($s9swiftTest6CircleCAA5ShapeAAWP), External: NO swiftTest.__DATA_CONST.__const +1c8
*/
expr -f float -- 地址為浮點數(shù)的還原(lldb)
根據(jù)上述分析,大致得出的shape數(shù)據(jù)結(jié)構(gòu)
struct LGProtocolBox {
var heapObject: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeRawPointer
}
分析IR代碼得出確定的類型,此時只留var shape: Shape = Circle(10)這行代碼
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
//獲取Circle的metadata
%3 = call swiftcc %swift.metadata_response @"$s4main6CircleCMa"(i64 0) #10
//提取%3到%4,%4也是metadata
%4 = extractvalue %swift.metadata_response %3, 0
//$s4main6CircleCyACSdcfC ---> main.Circle.__allocating_init(Swift.Double) -> main.Circle
//創(chuàng)建Circle實例變量
%5 = call swiftcc %T4main6CircleC* @"$s4main6CircleCyACSdcfC"(double 1.000000e+01, %swift.type* swiftself %4)
//$s4main5shapeAA5Shape_pvp ---> main.shape : main.Shape
//%T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
//%swift.type = type { i64 }
//將metadata存入main.shape
store %swift.type* %4, %swift.type** getelementptr inbounds
(%T4main5ShapeP, %T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp", i32 0, i32 1), align 8
//$s4main6CircleCAA5ShapeAAWP ---> protocol witness table for main.Circle : main.Shape in main
//將數(shù)組的第一個元素也存進(jìn)main.shape中第三個元素中
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main6CircleCAA5ShapeAAWP", i32 0, i32 0),
i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp", i32 0, i32 2), align 8
//將創(chuàng)建Circle實例變量存入main.shape的第一個元素位置
store %T4main6CircleC* %5, %T4main6CircleC**
bitcast (%T4main5ShapeP* @"$s4main5shapeAA5Shape_pvp" to %T4main6CircleC**), align 8
ret i32 0
}
//s4main6CircleCAA5ShapeAAMc 存入第一個元素中
//s4main6CircleCAA5ShapeA2aDP4areaSdvgTW 存入第二個元素中(遵循協(xié)議的方法)
//$s4main6CircleCAA5ShapeAAMc ---> protocol conformance descriptor for main.Circle : main.Shape in main
//$s4main6CircleCAA5ShapeA2aDP4areaSdvgTW ---> protocol witness for main.Shape.area.getter : Swift.Double in conformance main.Circle : main.Shape in main
@"$s4main6CircleCAA5ShapeAAWP" = hidden constant [2 x i8*]
[i8* bitcast (%swift.protocol_conformance_descriptor* @"$s4main6CircleCAA5ShapeAAMc" to i8*),
i8* bitcast (double (%T4main6CircleC**, %swift.type*, i8**)* @"$s4main6CircleCAA5ShapeA2aDP4areaSdvgTW" to i8*)], align 8
可以分析出TargetWitnessTable
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafeRawPointer
var witnessMethod: UnsafeRawPointer
}
關(guān)于protocol_conformance_descriptor,我們可以去源碼Metadata.h中找到
/// A witness table for a protocol.
///
/// With the exception of the initial protocol conformance descriptor,
/// the layout of a witness table is dependent on the protocol being
/// represented.
template <typename Runtime>
class TargetWitnessTable {
/// The protocol conformance descriptor from which this witness table
/// was generated.
ConstTargetMetadataPointer<Runtime, TargetProtocolConformanceDescriptor>
Description;
public:
const TargetProtocolConformanceDescriptor<Runtime> *getDescription() const {
return Description;
}
};
這里的Description就是我們要找的
進(jìn)入TargetProtocolConformanceDescriptor找到成員
/// The protocol being conformed to.
TargetRelativeContextPointer<Runtime, TargetProtocolDescriptor> Protocol;
// Some description of the type that conforms to the protocol.
TargetTypeReference<Runtime> TypeRef;
/// The witness table pattern, which may also serve as the witness table.
RelativeDirectPointer<const TargetWitnessTable<Runtime>> WitnessTablePattern;
/// Various flags, including the kind of conformance.
ConformanceFlags Flags;
進(jìn)入TargetProtocolDescriptor
struct TargetProtocolDescriptor final
: TargetContextDescriptor<Runtime>,
swift::ABI::TrailingObjects<
TargetProtocolDescriptor<Runtime>,
TargetGenericRequirementDescriptor<Runtime>,
TargetProtocolRequirement<Runtime>>
{
...
/// The name of the protocol.
TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name;
/// The number of generic requirements in the requirement signature of the
/// protocol.
uint32_t NumRequirementsInSignature;
/// The number of requirements in the protocol.
/// If any requirements beyond MinimumWitnessTableSizeInWords are present
/// in the witness table template, they will be not be overwritten with
/// defaults.
uint32_t NumRequirements;
/// Associated type names, as a space-separated list in the same order
/// as the requirements.
RelativeDirectPointer<const char, /*Nullable=*/true> AssociatedTypeNames;
...
}
根據(jù)源碼補充TargetWitnessTable數(shù)據(jù)結(jié)構(gòu),并驗證
struct LGProtocolBox {
var heapObject: UnsafeRawPointer
var unkown1: UnsafeRawPointer
var unkown2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafePointer<TargetWitnessTable>
}
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafeMutablePointer<ProtocolConformanceDescriptor>
var witnessMethod: UnsafeRawPointer
}
struct ProtocolConformanceDescriptor {
var protocolDesc: TargetRelativeDirectPointer<TargetProtocolDescriptor>
var typeRef: UnsafeRawPointer
var witnessTablePattern: UnsafeRawPointer
var flags: UInt32
}
//TargetProtocolDescriptor 繼承自 TargetContextDescriptor
struct TargetProtocolDescriptor {
var flags: UInt32
var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
var name: TargetRelativeDirectPointer<CChar>
var numRequirementsInSignature: UInt32
var numRequirements: UInt32
var associatedTypeNames: TargetRelativeDirectPointer<CChar>
}
var ptr = withUnsafePointer(to: &shape){UnsafeRawPointer($0).assumingMemoryBound(to: LGProtocolBox.self)}
var descPtr = ptr.pointee.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset()
print(String(cString: descPtr.pointee.name.getMeasureRelativeOffset())) // Shape
print(ptr.pointee.witness_table.pointee.witnessMethod) // 0x0000000100004d40
//本質(zhì)上執(zhí)行area中g(shù)et,還是通過Circle找到witness-table(PWT)再找到對應(yīng)的的函數(shù)地址,開始調(diào)用
終端還原一下0x0000000100004d40
? nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004d40
0000000100004d40 t _$s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW
? xcrun swift-demangle s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW
$s9swiftTest6CircleCAA5ShapeA2aDP4areaSdvgTW ---> protocol witness for swiftTest.Shape.area.getter : Swift.Double in conformance swiftTest.Circle : swiftTest.Shape in swiftTest
0x0000000100004d40就是Circle中遵循協(xié)議并實現(xiàn)area中g(shù)et的函數(shù)地址
總結(jié):
- 每個遵守了協(xié)議的類,都會有自己的PWT(Protocol Witness Table),遵守的協(xié)議函數(shù)越多,PWT中存儲的函數(shù)地址就越多
- PWT的本質(zhì)就是一個指針數(shù)組,第一個元素存儲
ProtocolConformanceDescriptor,其后面存儲的是函數(shù)地址 - PWT的數(shù)量與協(xié)議數(shù)量一致
三.Existential Container
我們之前總結(jié)的LGProtocolBox,就稱為Existential Container
struct LGProtocolBox {
var valueBuffer1: UnsafeRawPointer
var valueBuffer2: UnsafeRawPointer
var valueBuffer3: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafePointer<TargetWitnessTable>
}
為什么要有Existential Container?
//靜態(tài)類型為Circle
var circle: Circle = Circle(10)
靜態(tài)類型為Shape
var shape: Shape = Circle(10)
如果靜態(tài)類型為具體的類型,那么編譯器在編譯的時候知道分配多大的內(nèi)存空間來去存儲變量。
但是靜態(tài)類型為協(xié)議類型,那么編譯器在編譯的時候不知道分配多大的內(nèi)存空間去存儲。有可能是引用類型,有可能是值類型。因此使用Existential Container作為中間層存儲(40字節(jié))。
Existential Container是協(xié)議類型在編譯過程中不確定的編譯器技術(shù)。
Existential Container為什么要存儲metadata?
對于我們Existential Container也需要記錄它的metadata,需要記錄它的真實類型信息。當(dāng)我們調(diào)用屬性或方法的時候才能找到metadata。
1.小容量數(shù)據(jù)
小容量數(shù)據(jù)大小不超過24字節(jié)的數(shù)據(jù),直接存放在Value Buffer
Value Buffer指的是Existential Container前3個8字節(jié)
例如:Circle只有一個變量
protocol Shape {
var area: Double { get }
}
struct Circle: Shape {
var radious: Double
init(_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return radious * radious * Double.pi
}
}
}
var circle: Shape = Circle(10)
/*
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x00000001000102b8
0 elements
(lldb) x/8g 0x00000001000102b8
0x1000102b8: 0x4024000000000000 0x0000000000000000
0x1000102c8: 0x0000000000000000 0x000000010000c308
0x1000102d8: 0x000000010000c2f0 0x0000000000000000
0x1000102e8: 0x0000000000000000 0x0000000000000000
通過這里,可以看出第一個Value Buffer直接存放了10
(lldb) expr -f float -- 0x4024000000000000
(Int) $R1 = 10
(lldb)
*/
例如:Circle有3個變量(占滿24字節(jié))
protocol Shape {
var area: Double { get }
}
struct Circle: Shape {
var radious: Double
var radious1 = 10
var radious2 = 10
init(_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return radious * radious * Double.pi
}
}
}
var circle: Shape = Circle(10)
/*
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x00000001000102b8
0 elements
(lldb) x/8g 0x00000001000102b8
0x1000102b8: 0x4024000000000000 0x000000000000000a
0x1000102c8: 0x000000000000000a 0x000000010000c360
0x1000102d8: 0x000000010000c2f0 0x0000000000000000
0x1000102e8: 0x0000000000000000 0x0000000000000000
(lldb)
第一個Value Buffer直接存放了10.0
第二個Value Buffer直接存放了10
第三個Value Buffer直接存放了10
剛好使用完Value Buffer空間
*/
2.大容量數(shù)據(jù)
大容量數(shù)據(jù)大小超過24字節(jié)的數(shù)據(jù),通過堆區(qū)分配,存儲堆空間地址
例如:我們之前觀察的shape,存放的是Heap Pointer
例如:Circle有4個變量(32字節(jié))
protocol Shape {
var area: Double { get }
}
struct Circle: Shape {
var radious: Double
var radious1 = 10
var radious2 = 10
var radious3 = 10
init(_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return radious * radious * Double.pi
}
}
}
var circle: Shape = Circle(10)
/*
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x00000001000102c8
0 elements
(lldb) x/8g 0x00000001000102c8
0x1000102c8: 0x0000000101522120 0x0000000000000000
0x1000102d8: 0x0000000000000000 0x000000010000c388
0x1000102e8: 0x000000010000c318 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x0000000000000000
此時的Value Buffer1存放了開辟的堆空間地址
(lldb) x/8g 0x0000000101522120
0x101522120: 0x000000010000c300 0x0000000000000003
0x101522130: 0x4024000000000000 0x000000000000000a
0x101522140: 0x000000000000000a 0x000000000000000a
0x101522150: 0x0000000000000000 0x0000000000000000
0x0000000101522120 ---> 開辟的堆空間內(nèi)存地址
除去metadata及refCount后,依次存儲4條數(shù)據(jù)
(lldb) x/8g 0x000000010000c300
0x10000c300: 0x0000000000000400 0x0000000000000010
0x10000c310: 0x000000010000b944 0x000000010000a640
0x10000c320: 0x0000000100004de0 0x0000000100004e80
0x10000c330: 0x00000001000044e0 0x0000000100004eb0
這里的metadata指的是開辟的內(nèi)存空間的metadata,此時類型為HeapLocalVariable(0x400)。
如果Circle是引用類型,堆空間的metadata和Existential Container中的metadata是一致的。
此時算小容量存儲,將Heap Pointer存到Value Buffer1上。因此外部的metadata和內(nèi)部的是一致的
(lldb) x/8g 0x000000010000c388
0x10000c388: 0x0000000000000200 0x000000010000a690
0x10000c398: 0x0000000800000000 0x0000001800000010
0x10000c3a8: 0x0000000100005d40 0x00000001000044e0
0x10000c3b8: 0x0000000100004730 0x0000000100004730
這里的metadata指的是遵循協(xié)議的metadata,也就是Circle的metadata。類型為Struct(0x200)
(lldb)
*/
總結(jié):
-
Existential Container是編譯器生成的一種特殊的數(shù)據(jù)類型,用于管理遵守了相同協(xié)議的協(xié)議類型,因為這些類型的內(nèi)存大小不一致,所以通過當(dāng)前的Existential Container統(tǒng)一管理 - 對于小容量的數(shù)據(jù),直接存儲在
Value Buffer - 對于大容量的數(shù)據(jù),通過堆區(qū)分配,存儲堆空間的地址
四.寫時復(fù)制
當(dāng)存放大容量的數(shù)據(jù)時,值類型數(shù)據(jù)會放入堆空間內(nèi)存,如果此時去修改它,會發(fā)生什么變化?
protocol Shape {
var radious: Int { get set }
var radious1: Int { get set }
var radious2: Int { get set }
var radious3: Int { get set }
}
struct Circle: Shape {
var radious = 10
var radious1 = 20
var radious2 = 30
var radious3 = 40
}
var circle: Shape = Circle()
var circle1 = circle
/*
修改radious前的LLDB調(diào)試信息
(lldb) po withUnsafePointer(to: &circle) {print($0)}
0x00000001000102e8
0 elements
(lldb) x/8g 0x00000001000102e8
0x1000102e8: 0x000000010b2c1b80 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x000000010000c3e0
0x100010308: 0x000000010000c318 0x000000010b2c1b80
0x100010318: 0x0000000000000000 0x0000000000000000
(lldb) po withUnsafePointer(to: &circle1) {print($0)}
0x0000000100010310
0 elements
(lldb) x/8g 0x0000000100010310
0x100010310: 0x000000010b2c1b80 0x0000000000000000
0x100010320: 0x0000000000000000 0x000000010000c3e0
0x100010330: 0x000000010000c318 0x0000000000000000
0x100010340: 0x0000000000000000 0x0000000000000000
(lldb)
此時發(fā)現(xiàn)circle和circle1的ValueBuffer中存儲是同一個堆空間地址0x000000010b2c1b80
*/
circle.radious = 0
/*
修改radious后的LLDB調(diào)試信息
(lldb) x/8g 0x00000001000102e8
0x1000102e8: 0x0000000100706400 0x0000000000000000
0x1000102f8: 0x0000000000000000 0x000000010000c3e0
0x100010308: 0x000000010000c318 0x000000010b2c1b80
0x100010318: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x0000000100706400
0x100706400: 0x00007ff84223c1f8 0x0000000000000003
0x100706410: 0x0000000000000000 0x0000000000000014
0x100706420: 0x000000000000001e 0x0000000000000028
0x100706430: 0x0000000000000003 0x00007ff84223a3d8
(lldb)
(lldb) x/8g 0x0000000100010310
0x100010310: 0x000000010b2c1b80 0x0000000000000000
0x100010320: 0x0000000000000000 0x000000010000c3e0
0x100010330: 0x000000010000c318 0x0000000000000000
0x100010340: 0x0000000000000000 0x0000000000000000
(lldb)
此時發(fā)現(xiàn),當(dāng)修改了circle的radious后,circle中的堆空間地址發(fā)生了變化
相當(dāng)于復(fù)制了一份到新的堆空間,然后修改了radious的值
*/
對于修改radious后,復(fù)制了一塊新的堆區(qū)空間來存儲的現(xiàn)象,就稱為寫時復(fù)制
修改radious的時候,會去判斷堆空間的引用計數(shù)是是否為1,如果大于1就會進(jìn)行寫時復(fù)制,把當(dāng)前堆區(qū)數(shù)據(jù)復(fù)制一份然后再修改radious的值。
原理:對于協(xié)議類型,當(dāng)值類型的值超過了Value Buffer,會開辟了堆空間存儲。系統(tǒng)在修改值的過程中,先去檢測引用計數(shù),如果引用計數(shù)大于1就會開辟內(nèi)存空間,否則的話就不開辟。因為數(shù)據(jù)本身還是值類型,修改值的同時不能影響其它數(shù)據(jù),不能表現(xiàn)為引用類型,也就是寫時復(fù)制的原因。
好處:針對值類型,提高內(nèi)存指針的利用率,降低堆區(qū)的內(nèi)存的消耗,從而提高性能。
問題1:如果將Circle改為class,會是什么情況?還會有寫時復(fù)制嗎?
首先要了解一個問題,為什么要寫時復(fù)制。當(dāng)Circle是值類型時,此時circle1修改了值,能影響circle的值嗎?答案是不能的。因此,在協(xié)議類型修改circle1的值時,為了不影響circle的值,所以有了寫時復(fù)制,來保證原有值不會被修改。
那么我們再來分析如果circle1和circle引用類型,引用類型修改會影響其它一條數(shù)據(jù),那么此時需要寫時復(fù)制嗎?肯定也不是需要的。所以對于引用類型來說,就沒有寫時復(fù)制的概念。
五.還原TargetProtocolMetadata
在源碼中并未找到關(guān)于TargetProtocolMetadata相關(guān)信息,這里由Mirror源碼解析中介紹到的TargetProtocolMetadata拓展研究得出的。
如果誰知道關(guān)于TargetProtocolMetadata文檔,希望告訴一下,謝謝。
//指針數(shù)組
//第一個元素ProtocolConformanceDescriptor,從二個開始存放的才是函數(shù)指針
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafeMutablePointer<ProtocolConformanceDescriptor>
var witnessMethod: UnsafeRawPointer
//如果還有協(xié)議函數(shù),跟在后面依次排列
}
//記錄的是遵守協(xié)議的一些信息
struct ProtocolConformanceDescriptor {
var protocolDesc: TargetRelativeDirectPointer<TargetProtocolDescriptor>
// Some description of the type that conforms to the protocol.
var typeRef: UnsafeRawPointer
// The witness table pattern, which may also serve as the witness table.
var witnessTablePattern: UnsafeRawPointer
// Various flags, including the kind of conformance.
//標(biāo)志位
var flags: UInt32
}
//TargetProtocolDescriptor 繼承自 TargetContextDescriptor
struct TargetProtocolDescriptor {
//TargetContextDescriptor中的數(shù)據(jù)
var flags: UInt32
var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
//協(xié)議名稱
var name: TargetRelativeDirectPointer<CChar>
// The number of generic requirements in the requirement signature of the
// protocol.
var numRequirementsInSignature: UInt32
//需要遵循協(xié)議的數(shù)量
var numRequirements: UInt32
// Associated type names, as a space-separated list in the same order
// as the requirements.
//關(guān)聯(lián)類型名稱
var associatedTypeNames: TargetRelativeDirectPointer<CChar>
}
struct TargetProtocolMetadata {
var type: Any.Type
var witness_table: UnsafePointer<TargetWitnessTable>
}
//還原TargetProtocolMetadata
protocol MyProtocol {
func test()
}
struct LGTeacher: MyProtocol {
func test() {}
}
var type: MyProtocol.Type = LGTeacher.self
let protocolMetadata = unsafeBitCast(type, to: TargetProtocolMetadata.self)
print(protocolMetadata.type) //LGTeacher,遵循協(xié)議的類型
print(String(cString: protocolMetadata.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol
print(protocolMetadata.witness_table.pointee.witnessMethod) // 函數(shù)地址0x0000000100004cb0
/*
使用終端還原mach-o中的0x0000000100004cb0
? nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004cb0
0000000100004cb0 t _$s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW
? xcrun swift-demangle s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW
$s9swiftTest9LGTeacherVAA10MyProtocolA2aDP4testyyFTW ---> protocol witness for swiftTest.MyProtocol.test() -> () in conformance swiftTest.LGTeacher : swiftTest.MyProtocol in swiftTest
得出這個函數(shù)地址就是遵循MyProtocol協(xié)議的函數(shù)地址,也就是LGTeacher中的test函數(shù)地址
*/
六.問題探究
1.如果有一個類遵循了2個協(xié)議,那么Existential Container里的witness table里的數(shù)據(jù)是怎么存放的?
我們知道witness table是一個指針數(shù)組,第一條數(shù)據(jù)存放的是協(xié)議信息,后續(xù)數(shù)據(jù)存放的是遵循協(xié)議的函數(shù)地址
protocol Myprotocol {
func test()
}
protocol MyProtocol1 {
func test1()
}
typealias CustomProtocol = Myprotocol & MyProtocol1
class LGTeacher: CustomProtocol {
func test(){
print("test")
}
func test1(){
print("test1")
}
}
var t: CustomProtocol = LGTeacher()
IR代碼分析
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
%3 = call swiftcc %swift.metadata_response @"$s4main9LGTeacherCMa"(i64 0) #4
%4 = extractvalue %swift.metadata_response %3, 0
%5 = call swiftcc %T4main9LGTeacherC* @"$s4main9LGTeacherCACycfC"(%swift.type* swiftself %4)
//$s4main1tAA11MyProtocol1_AA10Myprotocolpvp ---> main.t : main.MyProtocol1 & main.Myprotocol
//存metadata到index1
store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 1), align 8
//$s4main9LGTeacherCAA11MyProtocol1AAWP ---> protocol witness table for main.LGTeacher : main.MyProtocol1 in main
//存Protocol1的協(xié)議見證表到index2
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main9LGTeacherCAA11MyProtocol1AAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 2), align 8
//$s4main9LGTeacherCAA10MyprotocolAAWP ---> protocol witness table for main.LGTeacher : main.Myprotocol in main
//存Protocol1的協(xié)議見證表到inde3
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main9LGTeacherCAA10MyprotocolAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main11MyProtocol1_AA10Myprotocolp, %T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp", i32 0, i32 3), align 8
//將LGTeacher變量存入第一個元素
store %T4main9LGTeacherC* %5, %T4main9LGTeacherC** bitcast (%T4main11MyProtocol1_AA10Myprotocolp* @"$s4main1tAA11MyProtocol1_AA10Myprotocolpvp" to %T4main9LGTeacherC**), align 8
ret i32 0
}
通過IR代碼可以得出Existential Container數(shù)據(jù)結(jié)構(gòu)
struct LGProtocolBox {
//此時這里存放的是LGTeacher實例的Heap Pointer
var valueBuffer1: UnsafeRawPointer
//0x0,空閑
var valueBuffer2: UnsafeRawPointer
//0x0,空閑
var valueBuffer3: UnsafeRawPointer
//LGTeacher的metadata
var metadata: UnsafeRawPointer
//MyProtocol1的witness_table
var witness_table: UnsafePointer<TargetWitnessTable>
//MyProtocol的witness_table
var witness_table1: UnsafePointer<TargetWitnessTable>
}
注意哦,此時的Existential Container大小就不再是40了,而是48
通過代碼驗證,當(dāng)然這里也可以使用LLDB命令驗證
protocol Myprotocol {
func test()
}
protocol MyProtocol1 {
func test1()
}
typealias CustomProtocol = Myprotocol & MyProtocol1
class LGTeacher: CustomProtocol {
func test(){}
func test1(){}
}
var t: CustomProtocol = LGTeacher()
//存在容器大小變?yōu)榱?8,每多一個協(xié)議,大小多8字節(jié)來存放witness_table
print(MemoryLayout.size(ofValue: t)) //48
let ptr = withUnsafePointer(to: &t) {
UnsafeRawPointer($0).assumingMemoryBound(to: LGProtocolBox.self)
}
print(String(cString: ptr.pointee.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol1
print(String(cString: ptr.pointee.witness_table1.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // Myprotocol
print(ptr.pointee.witness_table.pointee.witnessMethod) //0x0000000100004bb0
print(ptr.pointee.witness_table1.pointee.witnessMethod) //0x0000000100004b90
/*
? nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004bb0
0000000100004bb0 t _$s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW
? xcrun swift-demangle s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW
$s9swiftTest9LGTeacherCAA11MyProtocol1A2aDP5test1yyFTW ---> protocol witness for swiftTest.MyProtocol1.test1() -> () in conformance swiftTest.LGTeacher : swiftTest.MyProtocol1 in swiftTest
? nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100004b90
0000000100004b90 t _$s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW
? xcrun swift-demangle s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW
$s9swiftTest9LGTeacherCAA10MyprotocolA2aDP4testyyFTW ---> protocol witness for swiftTest.Myprotocol.test() -> () in conformance swiftTest.LGTeacher : swiftTest.Myprotocol in swiftTest
*/
總結(jié):
Existential Container大小并不是固定是40字節(jié)的,當(dāng)多協(xié)議類型時,Existential Container會擴(kuò)容,每多一個協(xié)議會擴(kuò)容8字節(jié)來存入PWT。
2.如果有一個類遵循了2個協(xié)議,還原一下協(xié)議的metadata
其實邏輯與Existential Container一致,多了一個協(xié)議多了一個witness_table,那么在Metadata中也會多一個witness_table。
此時TargetProtocolMetadata數(shù)據(jù)結(jié)構(gòu)
struct TargetProtocolMetadata {
var type: Any.Type
var witness_table: UnsafePointer<TargetWitnessTable>
var witness_table1: UnsafePointer<TargetWitnessTable>
}
protocol Myprotocol {
func test()
}
protocol MyProtocol1 {
func test1()
}
typealias CustomProtocol = Myprotocol & MyProtocol1
class LGTeacher: CustomProtocol {
func test(){}
func test1(){}
}
var type: CustomProtocol.Type = LGTeacher.self
let ptr = unsafeBitCast(type, to: TargetProtocolMetadata.self)
print(ptr.type) //LGTeacher,遵循協(xié)議的類型
print(String(cString: ptr.witness_table.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // MyProtocol1
//函數(shù)地址就不一一還原了,這里肯定就是LGTeacher實現(xiàn)協(xié)議MyProtocol1中test函數(shù)的地址
print(ptr.witness_table.pointee.witnessMethod) // 函數(shù)地址0x0000000100004af0
print(String(cString: ptr.witness_table1.pointee.protocol_conformance_descriptor.pointee.protocolDesc.getMeasureRelativeOffset().pointee.name.getMeasureRelativeOffset())) // Myprotocol
print(ptr.witness_table1.pointee.witnessMethod) // 函數(shù)地址0x0000000100004ad0