Swift底层探索:协议&泛型
协议为方法、属性、以及其他特定的任务需求或功能定义蓝图。协议可被类、结构体、或枚举类型采纳以提供所需功能的具体实现。满足了协议中需求的任意类型都叫做遵循了该协议。
除了指定遵循类型必须实现的要求外,可以扩展一个协议以实现其中的一些需求或实现一个符合类型的可以利用的附加功能。
基本语法
语法格式
protocol SomeProtocol {
// protocol definition goes here
}
class
,struct
,enum
都可以遵循协议,如果要遵守多个协议,使用逗号分隔
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
类除外);
image.png由于
final
的类不会有子类,如果协议初始化器实现的类使用了final
标记,就不需要使用required
来修饰了。因为这样的类不能被继承。
- 协议要求只被能类遵循,加上
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
代码:
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
代码:
可以看到
object.test()
变成了静态调用,由于在extension
中声明的方法在调度过程中为静态调度,在编译的过程中地址就确定了。在函数执行的过程中就直接拿到地址调用了。类中是没有办法修改的(重写无效)。并且
PWT
中已经没有了对应方法的声明(实现肯定也找不到了):
sil_witness_table hidden HotpotCat: SomeProtocol module main {
}
猜想下如果类中不重写test()
方法,那么应该也是静态调度,object
和obejct1
都输出SomeProtocol test()
class HotpotCat: SomeProtocol {
// func test() {
// print("HotpotCat test()")
// }
}
//输出
SomeProtocol test()
SomeProtocol test()
对应的SIL
:
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
。
内存结构如下:
分析下
SIL
:image.png
可以看到取
cirlce
从load
变成了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 bestore
-d orcopy_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)
}
}
观察下内存结构:
那么在这里
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
存储到了value1
,height
存储到了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
前后查看变量内存分配,结果如下:
修改前
circle
和circle2
的heapObject
也就是value1
相同0x000000010590de10
,修改后circle2
的heapobject
变成了0x000000010060b940
。这里也就验证了值类型(虽然超过了24字节存储到了堆上)写时赋值。如果是class
则不会改变。
image.png
- 结构体中
24
字节官方叫法是Value Buffer
-
Value Buffer
用来存储当前的值,如果超过存储的最大容量的话会开辟一块堆空间。针对值类型来说在赋值是会先拷贝heapobject地址(Copy on write
)。在修改时会先检测引用计数,如果引用计数大于1此时开辟新的堆空间把要修改的内容拷贝到新的堆空间(这么做为了提升性能)。
泛型
泛型代码能根据所定义的要求写出可以用于任何类型的灵活的、可复用的函数。可以编写出可复用、意图表达清晰、抽象的代码。
泛型是Swift
最强大的特性之一,很多Swift
标准库是基于泛型代码构建的。例如,Swift
的Array
和Dictionary
类型都是泛型集合。可以创建一个容纳Int
值的数组,或者容纳String
值的数组,甚至容纳任何Swift
可以创建的其他类型的数组。同样,可以创建一个存储任何指定类型值的字典,而且类型没有限制。
泛型所解决的问题:代码的复用性和抽象能力。
比如交换两个值,这里的值可以是Int
、Double
、String
。
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
可以自己推断合适的ItemType
为Int
。这个时候就可以写一个泛型版本了。
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")
}
}
当ItemType
是Double
的时候是找不到test
方法的。
泛型函数
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.h
中TargetValueWitnessTable
):
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
来管理类型的值。比如Int
、String
、Class
的复制销毁、创建以及是否需要引用计数。
上面代码流程大致如下:
- 询问metadata中VWT:size,stride分配内存空间
- 调用VWT-copy方法拷贝值
- 返回temp
- 调用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;
}
对于值类型通过内存copy
和move
进行内存拷贝。
对于引用类型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
由编译器生成,存储了该类型的size
、aligment
以及针对类型的基本内存操作。
当对泛型类型进行内存操作时(如:内存拷贝),最终会调用对应泛型类型的VWT
中的基本内存操作。泛型类型不同,对应的VWT
也不同。
总结:
- 对于一个值类型,如:
Int
。该类型copy
和move
操作会进行内存拷贝;destory
操作则不进行任何操作。 - 对于一个引用类型,如:
Class
。该类型的copy
操作会对引用计数+1
;move
操作会拷贝指针,而不更新引用计数;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
:
代码还原下:
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