Swift底层探索:协议&泛型

协议为方法、属性、以及其他特定的任务需求或功能定义蓝图。协议可被类、结构体、或枚举类型采纳以提供所需功能的具体实现。满足了协议中需求的任意类型都叫做遵循了该协议。
除了指定遵循类型必须实现的要求外,可以扩展一个协议以实现其中的一些需求或实现一个符合类型的可以利用的附加功能。

基本语法

语法格式

protocol SomeProtocol {
    // protocol definition goes here
}

classstructenum都可以遵循协议,如果要遵守多个协议,使用逗号分隔

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}

父类名放在遵循的协议名之前,用逗号隔开

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // class definition goes here
}

属性

  • 协议要求一个属性必须明确是可读或可读写的;


    image.png

    遵循协议的类型只要包含协议规定的读写属性就可以,比如协议规定属性是可读的,遵循的类型可以声明为读写的,反之不行。

  • 属性要求定义为变量类型;


    image.png
  • 协议要求遵循该协议的类型提供特定名字和类型的实例属性或类型属性(并不具体说明是存储属性还是计算属性,只要求有特定的名称和类型);

protocol SomeProtocol {
    static var name: String {get}
    var age: Int { get set}
}

struct Hotpot: SomeProtocol {
    static var name: String {
        get {
            "hotpot"
        }
    }
    
    var age: Int = 18
}

struct Cat: SomeProtocol {
    var myAge: Int
    static var name: String  = "hotpot"
    var age: Int {
        get {
            18
        }
        set {
            myAge = newValue + 1
        }
    }
}

方法

协议中可以定义实例方法和类方法,只需要定义当前方法的名称,参数列表和返回值,不需要大括号和方法的主体。

protocol SomeProtocol {
    func something() -> String
    static func doSomething()
    func myThing()
}

extension SomeProtocol {
    func something() -> String {
        return "something"
    }
    
    static func doSomething() {
        print("doSomething")
    }
}

struct Hotpot: SomeProtocol {
    func myThing() {
        print("myThing")
    }
}

var hotpot = Hotpot()

Hotpot.doSomething()
hotpot.something()
hotpot.myThing()
  • 在协议的定义中,方法参数不能定义默认值
    image.png
  • 协议中定义初始化方法,在类中实现初始化器的时候必须使用required关键字(final类除外);

由于 final 的类不会有子类,如果协议初始化器实现的类使用了 final 标记,就不需要使用 required 来修饰了。因为这样的类不能被继承。

image.png
  • 协议要求只被能类遵循,加上AnyObject
    image.png
protocol SomeProtocol {
    init(age: Int)
}

struct Hotpot: SomeProtocol {
    var age: Int
    init(age: Int) {
        self.age = age
    }
}

class Cat: SomeProtocol {
    var age: Int
    required init(age: Int) {
        self.age = age
    }
}

协议作为类型

  • 作为函数、方法或初始化程序中的参数类型或返回类型;
  • 作为常量、变量或属性的类型;
  • 作为数组、字典或其他容器中项目的类型

常量、变量或属性的类型

protocol SomeProtocol {
    func test()
}

extension SomeProtocol {
    func test() {
        print("SomeProtocol test()")
    }
}

class HotpotCat: SomeProtocol {
    func test() {
        print("HotpotCat test()")
    }
}

let object: SomeProtocol = HotpotCat()
object.test()

let object1: HotpotCat = HotpotCat()
object1.test()

输出

HotpotCat test()
HotpotCat test()

这个没有争议,也符合预期。
观察下SIL代码:

image.png

object.test()通过PWT来调用,对应的PWT如下:

//协议目击表记录了test函数
sil_witness_table hidden HotpotCat: SomeProtocol module main {
  method #SomeProtocol.test: <Self where Self : SomeProtocol> (Self) -> () -> () : @protocol witness for main.SomeProtocol.test() -> () in conformance main.HotpotCat : main.SomeProtocol in main   // protocol witness for SomeProtocol.test() in conformance HotpotCat
}

对应的test()实现如下:

// protocol witness for SomeProtocol.test() in conformance HotpotCat
//HotpotCat 遵循了协议并实现了方法之后的test函数的实现
sil private [transparent] [thunk] @protocol witness for main.SomeProtocol.test() -> () in conformance main.HotpotCat : main.SomeProtocol in main : $@convention(witness_method: SomeProtocol) (@in_guaranteed HotpotCat) -> () {
// %0                                             // user: %1
bb0(%0 : $*HotpotCat):
  %1 = load %0 : $*HotpotCat                      // users: %2, %3
  //HotpotCat类的函数表查找test函数(V-Table)
  %2 = class_method %1 : $HotpotCat, #HotpotCat.test : (HotpotCat) -> () -> (), $@convention(method) (@guaranteed HotpotCat) -> () // user: %3
  %3 = apply %2(%1) : $@convention(method) (@guaranteed HotpotCat) -> ()
  %4 = tuple ()                                   // user: %5
  return %4 : $()                                 // id: %5
} // end sil function 'protocol witness for main.SomeProtocol.test() -> () in conformance main.HotpotCat : main.SomeProtocol in main'

通过协议目击表调用的方式本质上也是通过V-Table查找调用。
那么如果SomeProtocol协议中的test函数注释掉呢?

protocol SomeProtocol {
//    func test()
}

输出:

SomeProtocol test()
HotpotCat test()

为什么?
继续查看SIL代码:

image.png

可以看到object.test()变成了静态调用,由于在extension中声明的方法在调度过程中为静态调度,在编译的过程中地址就确定了。在函数执行的过程中就直接拿到地址调用了。类中是没有办法修改的(重写无效)。
并且PWT中已经没有了对应方法的声明(实现肯定也找不到了):

sil_witness_table hidden HotpotCat: SomeProtocol module main {
}

猜想下如果类中不重写test()方法,那么应该也是静态调度,objectobejct1都输出SomeProtocol test()

class HotpotCat: SomeProtocol {
//    func test() {
//        print("HotpotCat test()")
//    }
}
//输出
SomeProtocol test()
SomeProtocol test()

对应的SIL:

image.png

sil_witness_table hidden HotpotCat: SomeProtocol module main {
  method #SomeProtocol.test: <Self where Self : SomeProtocol> (Self) -> () -> () : @protocol witness for main.SomeProtocol.test() -> () in conformance main.HotpotCat : main.SomeProtocol in main   // protocol witness for SomeProtocol.test() in conformance HotpotCat
}

// protocol witness for SomeProtocol.test() in conformance HotpotCat
sil private [transparent] [thunk] @protocol witness for main.SomeProtocol.test() -> () in conformance main.HotpotCat : main.SomeProtocol in main : $@convention(witness_method: SomeProtocol) <τ_0_0 where τ_0_0 : HotpotCat> (@in_guaranteed τ_0_0) -> () {
// %0                                             // user: %2
bb0(%0 : $*τ_0_0):
  // function_ref SomeProtocol.test()
  %1 = function_ref @(extension in main):main.SomeProtocol.test() -> () : $@convention(method) <τ_0_0 where τ_0_0 : SomeProtocol> (@in_guaranteed τ_0_0) -> () // user: %2
  %2 = apply %1<τ_0_0>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : SomeProtocol> (@in_guaranteed τ_0_0) -> ()
  %3 = tuple ()                                   // user: %4
  return %3 : $()                                 // id: %4
} // end sil function 'protocol witness for main.SomeProtocol.test() -> () in conformance main.HotpotCat : main.SomeProtocol in main'

总结:

  • 协议中声明方法,伴随着遵循协议的类生成一张PWT,协议目击表包含了类对协议的实现(如果类没有实现,则PWT实现是静态调度)。这个实现也是通过(V-Table/静态调度)找到类中/extension方法的实现来调度。也就是说PWT中声明方法是和协议中声明方法对应的。
  • 协议中没有声明方法,只是在协议扩展中给了默认实现,在编译过程中地址已经确定了,对于遵守协议的类来说无法重写方法。

PWT

上面了解到了协议中声明方法的调用,那么PWT存储在哪呢?内存大小一样么?

protocol Shape {
    var area: Double{ get }
}

extension Shape {
    var area: Double {
        0
    }
}

class Circle: Shape {
    var radious: Double

    init(_ radious: Double) {
        self.radious = radious
    }

    var area: Double{
        get {
            return radious * radious * 3.14
        }
    }
}

var circle: Shape = Circle.init(10.0)
print(MemoryLayout.size(ofValue: circle))
print(MemoryLayout.stride(ofValue: circle))

var circle1: Circle = Circle.init(10.0)
print(MemoryLayout.size(ofValue: circle1))
print(MemoryLayout.stride(ofValue: circle1))

输出:

40
40
8
8

可以看到声明为Shape类型,大小变为了40
内存结构如下:

image.png

分析下SIL:
image.png

可以看到取cirlceload变成了init_existential_addr,
使用容器包含了Shape类型,使用这个类型初始化circle变量。相当于对circle包装了一层。

sil-instruction ::= 'init_existential_addr' sil-operand ',' sil-type

%1 = init_existential_addr %0 : $*P, $T
// %0 must be of a $*P address type for non-class protocol or protocol
//   composition type P
// $T must be an AST type that fulfills protocol(s) P
// %1 will be of type $*T', where T' is the maximally abstract lowering
//    of type T

Partially initializes the memory referenced by %0 with an existential container prepared to contain a value of type $T. The result of the instruction is an address referencing the storage for the contained value, which remains uninitialized. The contained value must be store-d or copy_addr-ed to in order for the existential value to be fully initialized. If the existential container needs to be destroyed while the contained value is uninitialized, deinit_existential_addr must be used to do so. A fully initialized existential container can be destroyed with destroy_addr as usual. It is undefined behavior to destroy_addr a partially-initialized existential container.

再通过IR分析下到底存储的是什么:

;{24字节,swift.type指针,二级指针}
%T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }

;heapobject
%T4main6CircleC = type <{ %swift.refcounted, %TSd }>

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  %3 = call swiftcc %swift.metadata_response @"type metadata accessor for main.Circle"(i64 0) #7
  %4 = extractvalue %swift.metadata_response %3, 0
 ;double 1.000000e+01 是 10,%swift.type* 类的元数据
  %5 = call swiftcc %T4main6CircleC* @"main.Circle.__allocating_init(Swift.Double) -> main.Circle"(double 1.000000e+01, %swift.type* swiftself %4)
  ;metadata存储%4对应 %T4main5ShapeP 结构体的%swift.type*。相当于把metadata放到了结构体中 { [24 x i8], metadata, i8** }
  store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"main.circle : main.Shape", i32 0, i32 1), align 8
  ;PWT地址存储到 %T4main5ShapeP 到 i8** { [24 x i8], metadata, PWT }
  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(heapObject)放到%T4main6CircleC中 { heapObject, metadata, PWT }
  store %T4main6CircleC* %5, %T4main6CircleC** bitcast (%T4main5ShapeP* @"main.circle : main.Shape" to %T4main6CircleC**), align 8
  ret i32 0
}

也就是最终结构是{ heapObject, metadata, PWT }
代码还原下:

struct ProtocolData {
    //24字节
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //metadata
    var metadata: UnsafeRawPointer// 这里存储为了找到 VWT(Value Witness Table)
    //pwt
    var pwt: UnsafeRawPointer
}

完整代码:

protocol Shape {
    var area: Double{ get }
}

extension Shape {
    var area: Double {
        0
    }
}

class Circle: Shape {
    var radious: Double

    init(_ radious: Double) {
        self.radious = radious
    }

    var area: Double{
        get {
            return radious * radious * 3.14
        }
    }
}

struct ProtocolData {
    //24字节
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //metadata
    var metadata: UnsafeRawPointer// 这里存储为了找到 VWT(Value Witness Table)
    //pwt
    var pwt: UnsafeRawPointer
}

withUnsafePointer(to: &circle) { ptr  in
    ptr.withMemoryRebound(to: ProtocolData.self, capacity: 1) { protocolPtr in
        print(protocolPtr.pointee)
    }
}

观察下内存结构:

image.png

那么在这里PWT就是0x0000000100004028,符号表中查看下:

➜  ~ nm -p /Users/binxiao/Library/Developer/Xcode/DerivedData/SwiftProtocol-benaiiiiyumfmlauiejfynxbwtfi/Build/Products/Debug/SwiftProtocol | grep 0000000100004028
0000000100004028 S _$s13SwiftProtocol6CircleCAA5ShapeAAWP
➜  ~ xcrun swift-demangle s13SwiftProtocol6CircleCAA5ShapeAAWP
$s13SwiftProtocol6CircleCAA5ShapeAAWP ---> protocol witness table for SwiftProtocol.Circle : SwiftProtocol.Shape in SwiftProtocol
➜  ~

可以看到确实是PWT,这里存储PWT的目的是调用的时候找到对应的方法。 这也就解释了最开始内存大小为40的原因。

上面还原的数据结构有3个value,我们分析了类,改为结构体再分析下:

protocol Shape {
    var area: Double{ get }
}

extension Shape {
    var area: Double {
        0
    }
}

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
        }
    }
}
struct ProtocolData {
    //24字节
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //metadata
    var metadata: UnsafeRawPointer// 这里存储为了找到 VWT(Value Witness Table)
    //pwt
    var pwt: UnsafeRawPointer
}

var circle: Shape = Rectangle.init(10, 20)

withUnsafePointer(to: &circle) { ptr  in
    ptr.withMemoryRebound(to: ProtocolData.self, capacity: 1) { protocolPtr in
        print(protocolPtr.pointee)
    }
}
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)
  %4 = extractvalue { double, double } %3, 0
  %5 = extractvalue { double, double } %3, 1
  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.circle : main.Shape", i32 0, i32 1), align 8
  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.circle : main.Shape", i32 0, i32 2), align 8
  ;width 存储到 8字节(0~7),也就会value1
  store double %4, double* getelementptr inbounds (%T4main9RectangleV, %T4main9RectangleV* bitcast (%T4main5ShapeP* @"main.circle : main.Shape" to %T4main9RectangleV*), i32 0, i32 0, i32 0), align 8
  ;height 存储到 8字节 (8~15),value2
  store double %5, double* getelementptr inbounds (%T4main9RectangleV, %T4main9RectangleV* bitcast (%T4main5ShapeP* @"main.circle : main.Shape" to %T4main9RectangleV*), i32 0, i32 1, i32 0), align 8
  ret i32 0
}
image.png

可以看到width存储到了value1height存储到了value2中(这里如果是class的话仍然还是只存在value1,由于存储的是heapobject)。
那么多于3个值呢?

class Rectangle: Shape{
    var width, height: Double
    var width1 = 30
    var wifth2 = 40
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double {
        get {
            return width * height
        }
    }
}

image.png

可以看到value1变成了heapobject存在了堆空间。

总结:协议类型内存存储结构

  • 对于值类型来说多于24字节会转变为heapobject地址存在value1,值存储到堆空间,少于等于24字节值会存储在24字节内存空间中;
  • 对于引用类型来说value1直接存储heapobject

copy on write

struct ProtocolData {
    //24字节
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    //metadata
    var metadata: UnsafeRawPointer// 这里存储为了找到 VWT(Value Witness Table)
    //pwt
    var pwt: UnsafeRawPointer
}

protocol Shape {
    var width: Double { get set }
    var height: Double { get set }
    var area: Double{ get }
}

extension Shape {
    var area: Double {
        0
    }
}

struct Rectangle: Shape{
    var width, height: Double
    var width1 = 30, height1 = 40
    
    init(_ width: Double, _ height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double {
        get {
            return width * height
        }
    }
}

var circle: Shape = Rectangle.init(10, 20)

var circle2: Shape = circle

withUnsafePointer(to: &circle) { ptr  in
    ptr.withMemoryRebound(to: ProtocolData.self, capacity: 1) { protocolPtr in
        print(protocolPtr.pointee)
    }
}

withUnsafePointer(to: &circle2) { ptr  in
    ptr.withMemoryRebound(to: ProtocolData.self, capacity: 1) { protocolPtr in
        print(protocolPtr.pointee)
    }
}

circle2.width = 50

分别在修改width前后查看变量内存分配,结果如下:

image.png

修改前circlecircle2heapObject也就是value1相同0x000000010590de10,修改后circle2heapobject变成了0x000000010060b940。这里也就验证了值类型(虽然超过了24字节存储到了堆上)写时赋值。如果是class则不会改变。

image.png
  • 结构体中24字节官方叫法是Value Buffer
  • Value Buffer用来存储当前的值,如果超过存储的最大容量的话会开辟一块堆空间。针对值类型来说在赋值是会先拷贝heapobject地址(Copy on write)。在修改时会先检测引用计数,如果引用计数大于1此时开辟新的堆空间把要修改的内容拷贝到新的堆空间(这么做为了提升性能)。
image.png

泛型

泛型代码能根据所定义的要求写出可以用于任何类型的灵活的、可复用的函数。可以编写出可复用、意图表达清晰、抽象的代码。
泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。例如,SwiftArrayDictionary 类型都是泛型集合。可以创建一个容纳 Int 值的数组,或者容纳 String 值的数组,甚至容纳任何 Swift 可以创建的其他类型的数组。同样,可以创建一个存储任何指定类型值的字典,而且类型没有限制。
泛型所解决的问题:代码的复用性和抽象能力。
比如交换两个值,这里的值可以是IntDoubleString

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

在调用函数的时候会用实际类型替换。

类型约束

在一个类型参数后面放置协议或者是类型。比如要求我们的类型参数T遵循Equatable协议:

func test<T: Equatable>(_ a: T, _ b: T) -> Bool {
    return a == b
}

关联类型

关联类型给协议中用到的类型一个占位符名称(关联类型只能用于协议)。直到采纳协议时,才指定用于该关联类型的实际类型。关联类型通过 associatedtype 关键字指定。

protocol Container {
    //占位符
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

struct Stack<Element>: Container {
    typealias ItemType = Int
    var items = [ItemType]()
    mutating func push(_ item: ItemType) {
        items.append(item)
    }
    mutating func pop() -> ItemType {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: ItemType) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> ItemType {
        return items[i]
    }
}

在遵循了协议实现的时候才去指定真正类型。这里可以不指定,Swift可以自己推断合适的ItemTypeInt。这个时候就可以写一个泛型版本了。

protocol Container {
    //占位符
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

struct Stack<Element>: Container {
    
    // original Stack<Element> implementation
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

当然可以给关联类型添加约束。

protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Where语句

protocol Container {
    //占位符
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

struct Stack<Element>: Container {
    
    // original Stack<Element> implementation
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

//T1.ItemType == T2.ItemType 表示类型相等,T1.ItemType: Equatable 表示都遵循Equatable
func compare<T1: Container, T2: Container>(_ stack1: T1, _ stack2: T2) -> Bool where T1.ItemType == T2.ItemType, T1.ItemType: Equatable {
    guard stack1.count == stack2.count else {
        return false
    }
    
    for i in 0..<stack1.count {
        if stack1[i] != stack2[i] {
            return false
        }
    }
    return true
}

当然也可以直接写在extension中:

extension Container where ItemType: Equatable {}

有时候我们希望泛型指定类型的时候拥有特定功能,比如:

extension Container where ItemType == Int {
    func test() {
        print("only ItemType == Int")
    }
}

ItemTypeDouble的时候是找不到test方法的。

image.png

泛型函数

class HotpotCat {
    
}

func test<T>(_ value: T) -> T {
    //1.询问metadata中VWT:size,stride分配内存空间
    //2.调用VWT-copy方法拷贝值
    //3.返回temp
    //4.调用VWT-destory方法销毁局部变量
    let temp = value
    return temp
}

test(10)
test((10,20))
test(HotpotCat())

上面的例子,test方法接收任何类型的参数,在其中let temp = value有可能在堆上也有可能在栈上,那么系统是如何进行开辟空间和内存对齐的呢?这里是T系统如何知道?
看下对应的IR
main中调用

;泛型函数的调用。把Int类型的metadata作为了参数。
  call swiftcc void @"main.test<A>(A) -> A"(%swift.opaque* noalias nocapture sret %11, %swift.opaque* noalias nocapture %12, %swift.type* @"type metadata for Swift.Int")

image.png

所以当前泛型通过VWT来进行内存操作。

看下VWT的源码(在Metadata.hTargetValueWitnessTable):

template <typename Runtime> struct TargetValueWitnessTable {
   /// Return the size of this type.  Unlike in C, this has not been
  /// padded up to the alignment; that value is maintained as
  /// 'stride'.
  StoredSize getSize() const {
    return size;
  }

  /// Return the stride of this type.  This is the size rounded up to
  /// be a multiple of the alignment.
  StoredSize getStride() const {
    return stride;
  }

  /// Return the alignment required by this type, in bytes.
  StoredSize getAlignment() const {
    return flags.getAlignment();
  }
}

所以对于Swift类型的metadata中都存放了VWT来管理类型的值。比如IntStringClass的复制销毁、创建以及是否需要引用计数。
上面代码流程大致如下:

  1. 询问metadata中VWT:size,stride分配内存空间
  2. 调用VWT-copy方法拷贝值
  3. 返回temp
  4. 调用VWT-destory方法销毁局部变量

所以泛型在整个运行过程中的关键依赖于所谓的metadata。

metadataimpl.h源码中:
对于值类型NativeBox

  //调用析构函数
  static void destroy(T *value) {
    value->T::~T();
  }

  static T *initializeWithCopy(T *dest, T *src) {
    return new (dest) T(*src);
  }

  static T *initializeWithTake(T *dest, T *src) {
    T *result = new (dest) T(std::move(*src));
    src->T::~T();
    return result;
  }

对于值类型通过内存copymove进行内存拷贝。
对于引用类型RetainableBoxBase来说

  static void destroy(T *addr) {
    Impl::release(*addr);
  }

  static T *initializeWithCopy(T *dest, T *src) {
    *dest = Impl::retain(*src);
    return dest;
  }

  static T *initializeWithTake(T *dest, T *src) {
    *dest = *src;
    return dest;
  }
struct SwiftRetainableBox :
    RetainableBoxBase<SwiftRetainableBox, HeapObject*> {
  static HeapObject *retain(HeapObject *obj) {
    if (isAtomic) {
      swift_retain(obj);
    } else {
      swift_nonatomic_retain(obj);
    }
    return obj;
  }

  static void release(HeapObject *obj) {
    if (isAtomic) {
      swift_release(obj);
    } else {
      swift_nonatomic_release(obj);
    }
  }
};

以上也就是泛型如果管理传进来值的内存。
泛型类型使用VWT进行内存管理,VWT由编译器生成,存储了该类型的sizealigment以及针对类型的基本内存操作。
当对泛型类型进行内存操作时(如:内存拷贝),最终会调用对应泛型类型的VWT中的基本内存操作。泛型类型不同,对应的VWT也不同。

总结:

  • 对于一个值类型,如:Int。该类型copymove操作会进行内存拷贝;destory操作则不进行任何操作。
  • 对于一个引用类型,如:Class。该类型的copy操作会对引用计数+1move操作会拷贝指针,而不更新引用计数;destory操作会对引用计数-1

泛型的方法调用

如果把一个方法当做泛型传递进去呢?

func makeIncrementer() -> (Int) -> Int {
    var runningTotal = 10
    return {
        runningTotal += $0
        return runningTotal
    }
}

func test<T>(_ value: T) {

}

let makeInc = makeIncrementer()
test(makeInc)

分析下IR:

image.png

代码还原下:

struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refcount2: UInt32
}

struct Box<T> {
    var refCounted:HeapObject
    var value: T //捕获值
}

struct FunctionData<BoxType> {
    var ptr: UnsafeRawPointer //内嵌函数地址
    var captureValue: UnsafePointer<BoxType>? //捕获值地址
}

struct TestData<T> {
    var  ref: HeapObject
    var function: FunctionData<T>
}

func makeIncrementer() -> (Int) -> Int {
    var runningTotal = 10
    return {
        runningTotal += $0
        return runningTotal
    }
}

func test<T>(_ value: T) {
    let ptr  = UnsafeMutablePointer<T>.allocate(capacity: 1)
    ptr.initialize(to: value)
    //对于泛型T来说做了一层TestData桥接,目的是为了能够更好的解决不同值传递
    let ctx = ptr.withMemoryRebound(to: FunctionData<TestData<Box<Int>>>.self, capacity: 1) {
        $0.pointee.captureValue?.pointee.function.captureValue!
    }
    
    print(ctx?.pointee.value)
    ptr.deinitialize(count: 1)
    ptr.deallocate()
}

//{i8 *, swift type *}
let makeInc = makeIncrementer()
test(makeInc)

输出

Optional(10)
  • 对于泛型T来说做了一层TestData桥接,目的是为了能够更好的解决不同值传递。


    image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容