Swift底层进阶--014:泛型
- 泛型是为
Swift
编程提供灵活性的一种语法,可以提升代码的复用性和抽象能力- 例如:
Swift
的Array
和Dictionary
类型都是泛型集合- 泛型在函数、枚举、结构体、类中都能得到充分应用,它的引入可以起到占位符的作用,当类型暂时不确定时,只有等到调用函数时才能确定具体类型的时候可以引入泛型
func swap<T>(_ a: inout T, _ b: inout T) {
let tmp = a
a = b
b = tmp
}
例如交换两个值,使用泛型,可以无视参数类型,提升方法的复用性和抽象能力
类型约束
在⼀个类型参数后⾯放置协议或者类,⽐如要求类型参数
T
遵循Equatable
协议
func test<T: Equatable>(_ a: T ,_ b: T) -> Bool{
return a == b
}
例如未遵循
编译报错Equatable
协议的结构体类型,将实例对象传入方法,编译报错
func test<T: Equatable>(_ a: T ,_ b: T) -> Bool{
return a == b
}
struct LGTeacher: Equatable {
var age: Int = 18
}
var t1 = LGTeacher()
var t2 = LGTeacher()
var isTest = test(t1, t2)
只有遵循
Equatable
协议的类型可以使用方法
关联类型
在定义协议的时候,使⽤关联类型给协议中⽤到的类型起⼀个占位符名称
protocol StackProtocol {
associatedtype Item
}
struct LGStack: StackProtocol {
typealias Item = Int
private var items = [Item]()
mutating func push(_ item: Item){
items.append(item)
}
mutating func pop() -> Item?{
if items.isEmpty { return nil }
return items.removeLast()
}
}
上述代码,
StackProtocol
协议中使用associatedtype
关联类型定义的Item
,相当于占位符。在LGStack
中使用,必须先指定Item
的实际类型,使用typealias
关键字
结构体内指定
Int
类型显然不够灵活,这里使用泛型进一步修改:
protocol StackProtocol {
associatedtype Item
}
struct LGStack<T>: StackProtocol {
typealias Item = T
private var items = [Item]()
mutating func push(_ item: Item){
items.append(item)
}
mutating func pop() -> Item?{
if items.isEmpty { return nil }
return items.removeLast()
}
}
var t = LGStack<Int>()
Where语句
protocol StackProtocol {
associatedtype Item
var itemCount: Int{ get }
mutating func pop() -> Item?
func index(of index: Int) -> Item
}
struct LGStack<T>: StackProtocol{
typealias Item = T
private var items = [Item]()
var itemCount: Int{
get{
return items.count
}
}
mutating func push(_ item: Item){
items.append(item)
}
mutating func pop() -> Item?{
if items.isEmpty { return nil }
return items.removeLast()
}
func index(of index: Int) -> Item {
return items[index]
}
}
func compare<T1: StackProtocol, T2: StackProtocol>(_ stack1: T1, _ stack2: T2)-> Bool where T1.Item == T2.Item, T1.Item: Equatable {
guard stack1.itemCount == stack2.itemCount else {
return false
}
for i in 0..<stack1.itemCount {
if stack1.index(of: i) != stack2.index(of: i) {
return false
}
}
return true
}
var s1 = LGStack<Int>()
var s2 = LGStack<Int>()
var isTest = compare(s1, s2)
上述代码,
where
语句要求T1.Item
和T2.Item
必须类型相同,且T1.Item
必须遵循Equatable
协议,这意味着T2.Item
也必须遵循Equatable
协议
where
语句也可以在扩展中使用
extension LGStack where Item: Equatable{}
如果希望在当前泛型制定类型的时候拥有特定功能,可以使用这种写法:
protocol StackProtocol {
associatedtype Item
}
extension LGStack where Item == Int {
func test(){
print("Item == Int")
}
}
struct LGStack<T>: StackProtocol{
typealias Item = T
}
var stack = LGStack<Int>()
stack.test()
上述代码,如果指定的
编译报错Item
非Int
类型,将无法找到test
方法,编译报错
泛型函数
func testGenric<T>(_ value: T) -> T {
let tmp = value
return tmp
}
class LGTeacher {
}
var t = LGTeacher()
testGenric(10)
testGenric(t)
上述代码,
test
方法可以接收任何类型的参数,将value
赋值给tmp
,地址有可能在堆上,也有可能在栈上,那么系统是如何进行开辟空间和内存对齐的呢?
将上述代码生成IR文件:
IRswiftc -emit-ir main.swift | xcrun swift-demangle
所以泛型是通过
ValueWitnessTable
来进⾏内存操作,ValueWitnessTable
就是VWT
值目击表
通过源码对
ValueWitnessTableVWT
值目击表进行分析:
ValueWitnessTable
也是一个结构体,里面存储了size
、alignment
、stride
、copy
、destory
等信息。对于每一种数据类型,里面都存储了各自的VWT
,目的是用于管理类型的大小步长、对⻬⽅式、创建、销毁、复制、是否需要引用计数找到
NativeBoxNativeBox
NativeBox
用于值类型,例如Int
类型,系统会询问Metadata
中的VWT
,获取到size
、stride
进行内存空间的分配。然后调用VWT
的copy
方法拷贝值,将结果返回,返回后销毁局部变量找到
RetainableBoxBaseRetainableBoxBase
RetainableBoxBase
用于引用类型,同样是通过Metadata
中的VWT
进行一系列的操作
- 泛型类型使⽤
VWT
进⾏内存管理,VWT
由编译器⽣成,其存储了该类型的size
、aligment
(对⻬⽅式)以及针对该类型的基本内存操作- 当对泛型类型进⾏内存操作(如:内存拷⻉)时,最终会调⽤对应泛型类型的
VWT
中的基本内存操作。泛型类型不同,其对应的VWT
也不同- 对于⼀个值类型,例如:
Int
:该类型的copy
和move
操作会进⾏内存拷⻉。destroy
操作则不进⾏任何操作。- 对于⼀个引⽤类型,例如:
class
:该类型的copy
操作会对引⽤计数+1
。move
操作会拷⻉指针,⽽不会更新引⽤计数。destroy
操作会对引⽤计数-1
泛型的⽅法调⽤
func makeIncrement() -> (Int) -> Int{
var runningTotal = 10
return {
runningTotal += $0
return runningTotal
}
}
func testGen<T>(_ value: T) {
}
let makeInc = makeIncrement()
testGen(makeInc)
上述代码,将⼀个函数当做泛型传递,函数也是一个结构体,那传给泛型的会是一个结构体吗?
将上述代码生成IR文件:
IRswiftc -emit-ir main.swift | xcrun swift-demangle
使用
Swift
代码还原上述结论
struct HeapObject{
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
struct FuntionData<T>{
var ptr: UnsafeRawPointer
var captureValue: UnsafePointer<T>?
}
struct Box<T> {
var refCounted: HeapObject
var value: T
}
struct GenData<T>{
var ref: HeapObject
var function: FuntionData<T>
}
func makeIncrement() -> (Int) -> Int{
var runningTotal = 10
return {
runningTotal += $0
return runningTotal
}
}
func testGen<T>(_ value: T){
let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
ptr.initialize(to: value)
let ctx = ptr.withMemoryRebound(to: FuntionData<GenData<Box<Int>>>.self, capacity: 1) {
$0.pointee.captureValue?.pointee.function.captureValue!
}
print(ctx?.pointee.value)
}
let makeInc = makeIncrement()
testGen(makeInc)
//输出以下内容:
Optional(10)
GenData
相当于进行了一层包装,目的是解决不同值类型之间传递的问题