Swift -- 9.協(xié)議

一.協(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é)議要求一個屬性必須明確是getget和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ù)制,來保證原有值不會被修改。

那么我們再來分析如果circle1circle引用類型,引用類型修改會影響其它一條數(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容