一.泛型语法
1.函数中使用泛型
泛型的基本语法,首先我们要指定一个占位符T
(占位符也可以为其它字符),紧挨着写在函数名后面的一对尖括号(当前我们这个T
要遵循FloatingPoint
协议,计算乘积所必须);其次我们就可以使用T
来替换任意定义的函数形式参数。
func multiNum(x: T, y: T) -> T {
return x * y
}
2.结构体/类使用泛型
struct LGStack{
private var items = [Element]()
mutating func push(_ item: Element){
items.append(item)
}
mutating func pop() -> Element?{
if items.isEmpty { return nil }
return items.removeLast()
}
}
结构体与类使用泛型用法一致
3.协议使用泛型
注意:Protocol
不支持泛型参数,需要我们使用关联类型associatedtype
来代替。
protocol StackProtocol {
associatedtype Item
var itemCount: Item{ get }
mutating func pop() -> Item?
func index(of index: Int) -> Item
}
需要在遵循协议的地方,使用typealias Item = Int
来明确泛型参数的类型
protocol StackProtocol {
associatedtype Item
var itemCount: Item{ get }
mutating func pop() -> Item?
func index(of index: Int) -> Item
}
struct LGStack: StackProtocol{
//确定协议中的泛型参数类型
typealias Item = Int
var itemCount: Int {
get {
return items.count
}
}
private var items = [Item]()
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) -> Int {
return items[index]
}
}
当然也可以给关联类型添加约束,比如Item
必须遵循FixedWidthInteger
。加上过后,传入的Item
就必须遵守FixedWidthInteger
协议
associatedtype Item: FixedWidthInteger
此时添加一个EvenProtocol
protocol EvenProtocol: StackProtocol {
//1.Even必须遵循EventProtocol协议,
//2.限定Even关联类型里边的Item和当前的StackProtocol中Item类型必须是一致的
associatedtype Even: EvenProtocol where Even.Item == Item
func pushEven(_ item: Int) -> Even
}
extension LGStack: EvenProtocol{
// //方式1,直接指名函数中Even的类型为EvenProtocol
// func pushEven(_ item: Int) -> LGStack {
// var result = LGStack()
// if item % 2 == 0{
// result.push(item)
// }
// return result
// }
//方式二
typealias Even = LGStack
func pushEven(_ item: Int) -> Even {
//1.这里必须传入遵循EvenProtocol协议的数据
//2.传入的数据Item类型必须要和当前Item类型一致
var result = LGStack()
if item % 2 == 0{
result.push(item)
}
return result
}
}
在这个协议里,Even
是关联类型。拥有2个约束:它必须信息EventProtocol
,它的Item类型必须和容器里的Item类型相同。简单来说,就是传入的参数中的Item类型必须与当前Item类型一致
where
要求了关联类型必须遵循指定的协议,或者指定的类型形式参数和关联类型必须相同。泛型where
分句以where
关键字开头,后接关联类型的约束或类型和关联类型一致的关系。
//T1和T2必须遵守StackProtocol协议
//T1的关联类型Item必须和T2的Item一致
//T1必须遵循Equatable协议
//当然T1: StackProtocol, T2: StackProtocol,类型声明也可以定义到where后
func compare(_ 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..
二.类型擦除
1.类型擦除
//定义了一个协议
protocol DataFetch {
associatedtype DataType
func fetch(completion: ((Result) -> Void)?)
}
//用户数据
struct User {
let userId: Int
let name: String
}
struct UserData: DataFetch {
//关联类型为User
typealias DataType = User
func fetch(completion: ((Result) -> Void)?) {
let user = User(userId: 1001, name: "Kody")
completion?(.success(user))
}
}
class someViewController{
/*
此时let userData: DataFetch会报错
Swift是一门类型安全的语言,此时的userData传入的DataFetch的泛型协议类型,
由于关联类型并不能确定,因此编译器会报错
那么我们该怎么解决这个问题?
我们传入UserData有问题吗?
对于ViewController来说,并不关心你的数据类型是UserData还是其它Data,
只关心最后获取的User,也就是获取的数据。
如果我们还有VipData,如果传入UserData,那么Controller与UserData的耦合性就比较严重了。
*/
let userData: DataFetch
init() {
}
}
此时就需要一个中间层AnyDataFetch
进行类型擦除,解决ViewController与数据的耦合问题
//定义一个协议,获取数据的协议DataFetch
protocol DataFetch {
associatedtype DataType
func fetch(completion: ((Result) -> Void)?)
}
//定义一个User类
struct User {
let uid: Int
let name: String
}
//获取UserData的类
struct UserData: DataFetch {
typealias DataType = User
func fetch(completion: ((Result) -> Void)?) {
let user = User(uid: 1001, name: "Kody")
completion?(.success(user))
}
}
/*
定义一个中间类AnyDataFetch
AnyDataFetch实现了DataFetch的所有方法
在AnyDataFetch初始化过程中,实现协议的类型会被当做参数传入(依赖注入)
在AnyDataFetch实现具体方法fetch中,再转发实现协议的抽象类型。
*/
struct AnyDataFetch: DataFetch {
typealias DataType = T
//保存了当前类型的fetch函数,如果是UserData,那么保存的是UserData的fetch
private let _fetch: (((Result) -> Void)?) -> Void
//实现协议的类型会被当做参数传入(依赖注入)
init(_ completion: U) where U.DataType == T {
_fetch = completion.fetch
}
//执行保存的fetch函数
func fetch(completion: ((Result) -> Void)?) {
_fetch(completion)
}
}
class someViewController {
/*
此时使用的AnyDataFetch就已经把UserData类型擦除了
无论是UserData还是VipData都与someViewController没有耦合
通过中间层AnyDataFetch把具体类型给擦除了(隐藏掉了),通过这种方式就叫做类型擦除
*/
//具体类型为UserData,通过抽象隐藏了具体类型
let userData: AnyDataFetch
init(_ userData: AnyDataFetch) {
self.userData = userData
}
}
let userData = UserData()
let vc = someViewController(AnyDataFetch(userData))
vc.userData.fetch { result in
switch result {
case .success(let user):
print(user.name) // Kody
case .failure(let error):
print(error)
}
}
//如果再添加一个VipData
struct VipData: DataFetch {
typealias DataType = User
func fetch(completion: ((Result) -> Void)?) {
let vipUser = User(uid: 100, name: "Vip_Kody")
completion?(.success(vipUser))
}
}
let data = VipData()
let vc1 = someViewController(AnyDataFetch(data))
vc1.userData.fetch { result in
switch result {
case .success(let user):
print(user.name) // Vip_Kody
case .failure(let error):
print(error)
}
}
/*
此时你会发现,当我们增加VipData的数据类型时,我们的someViewController是不需要做任何的改动的
这样的效果,我们就称为类型擦除
关键点:使用AnyFetch作为中间层,传入了遵守协议的类型(依赖注入),存储了协议函数,使用AnyFetch执行函数,达到类型擦除的效果。
依赖注入:实现协议的类型当做参数传入
实际上,就是保存了函数,执行函数,把类型给擦除了的一个过程。
*/
通过中间层AnyDataFetch
把具体类型给擦除了(隐藏掉了),这种方式就叫做类型擦除
其实在Swift
中很多地方也使用到了类型擦除
,比如:Codable源码
、AnySequence
、AnyCollection
AnySequence
和AnyCollection
隐藏了泛型协议,统一的表达Sequence
和Collection
2.AnySequence使用案例
需求:迭代你的自定义属性User
,其中User
属性如下:
struct User {
var userId: Int
var name: String
}
使用Sequence
迭代自定义属性
//继承自Sequence才能够被便利/迭代
struct User: Sequence {
var userId: Int
var name: String
/*
必须实现的协议方法,返回一个迭代器(遵循IteratorProtocol)
*/
func makeIterator() -> CustomIterator {
return CustomIterator(obj: self)
}
}
//迭代器
struct CustomIterator: IteratorProtocol {
var children: Mirror.Children
init(obj: User) {
children = Mirror(reflecting: obj).children
}
/*
必须实现的方法,返回迭代时的内容
该函数会循环执行,当返回值为nil时,循环结束
*/
mutating func next() -> String? {
guard let child = children.popFirst() else { return nil }
return "\(child.label ?? "null") is \(child.value)"
}
}
let user = User(userId: 10, name: "小明")
for obj in user {
print(obj)
}
/*
打印结果
userId is 10
name is 小明
*/
如果这个时候,另一个自定义属性Vip
也需要迭代
struct Vip {
var vipdate: String
var viplevel: Int
var vipName: String
}
那么我们是否也像User
一样给Vip
添加一个Sequence
协议呢?
当然,这样做是可以的,但是这样做并不是我们想要的方式。
对于Vip
和User
来说它们的行为都是一致的,所以这里我们希望抽象出一个统一的协议
struct User: CustomDataSequence {
var userId: Int
var name: String
}
struct Vip: CustomDataSequence {
var vipdate: String
var viplevel: Int
var vipName: String
}
struct CustomDataIterator: IteratorProtocol {
var children: Mirror.Children
init(obj: Any) {
children = Mirror(reflecting: obj).children
}
/*
必须实现的方法,返回迭代时的内容
该函数会循环执行,当返回值为nil时,循环结束
*/
mutating func next() -> String? {
guard let child = children.popFirst() else { return nil }
return "\(child.label ?? "null") is \(child.value)"
}
}
protocol CustomDataSequence: Sequence {}
extension CustomDataSequence {
func makeIterator() -> CustomDataIterator {
return CustomDataIterator(obj: self)
}
}
如果此时我们想要定义个数组,同时有User
和Vip
数据
let user = User(userId: 10, name: "小明")
let vip = Vip(vipdate: "2022-02-24", viplevel: 100, vipName: "Vip3")
/*
如果这样使用的话会报错,和之前讲到的let data: DataFetch情况类似
对于协议泛型来说,并不能确定关联类型,因此编译器会报错
*/
let datas: [CustomDataSequence] = [user, vip]
这个时候就需要使用到AnySequence
将具体的Sequence
类型隐藏了,调用者知知道数组中的元素是一个可以迭代输出字符串类型的序列
//这里使用AnySequence作为中间类,来擦除泛型协议类型
let datas: [AnySequence] = [AnySequence(user), AnySequence(vip)]
for obj in datas {
for item in obj {
print(item)
}
}
/*
打印结果
userId is 10
name is 小明
vipdate is 2022-02-24
viplevel is 100
vipName is Vip3
*/
这就是AnySequence
上的类型擦除案例
三.泛型的工作原理
//了解泛型工作原理
/*
此时的编译器并不知道T是什么类型,可能是引用类型可能是值类型
*/
func testGeneric(_ value: T) -> T {
//对于我们的编译器来说,类型并不明确,编译器怎么知道temp应该分配多大的内存空间、步长、对齐的字段是多少
let temp = value
return temp
}
testGeneric(10)
猜想:此时应该是根据传入的类型来决断分配的内存空间大小
那么我们编译成IR代码来看看到底是怎么工作的
//testGeneric函数,实际上把%swift.type*传入进来了(metadata)
define hidden swiftcc void @"$s4main11testGenericyxxlF"(%swift.opaque* noalias nocapture sret(%swift.opaque) %0, %swift.opaque* noalias nocapture %1, %swift.type* %T) #0 {
entry:
//%swift.type = type { i64 } ,Metadata的结构体
%T1 = alloca %swift.type*, align 8
%temp.debug = alloca i8*, align 8
%2 = bitcast i8** %temp.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
//将T(函数的参数metadata)传入到我们的T1
store %swift.type* %T, %swift.type** %T1, align 8
%3 = bitcast %swift.type* %T to i8***
//注意:这里对%3去-1索引存到%4
%4 = getelementptr inbounds i8**, i8*** %3, i64 -1
//取出来的-1索引就是valueWitnessTable(值目击表,记录了size、stride、alignment)
%T.valueWitnesses = load i8**, i8*** %4, align 8, !invariant.load !32, !dereferenceable !33
%5 = bitcast i8** %T.valueWitnesses to %swift.vwtable*
%6 = getelementptr inbounds %swift.vwtable, %swift.vwtable* %5, i32 0, i32 8
%size = load i64, i64* %6, align 8, !invariant.load !32
%7 = alloca i8, i64 %size, align 16
call void @llvm.lifetime.start.p0i8(i64 -1, i8* %7)
%8 = bitcast i8* %7 to %swift.opaque*
store i8* %7, i8** %temp.debug, align 8
//%9取出valueWitnesses中索引2的值
%9 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 2
//%10由%9得来,i8**变为i8*
%10 = load i8*, i8** %9, align 8, !invariant.load !32
//使用Copy初始化函数,这样可以发现,都是通过valueWitness来初始化的
%initializeWithCopy = bitcast i8* %10 to %swift.opaque* (%swift.opaque*, %swift.opaque*, %swift.type*)*
%11 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %8, %swift.opaque* noalias %1, %swift.type* %T) #3
%12 = call %swift.opaque* %initializeWithCopy(%swift.opaque* noalias %0, %swift.opaque* noalias %8, %swift.type* %T) #3
//%13取出valueWitnesses中索引1的值
%13 = getelementptr inbounds i8*, i8** %T.valueWitnesses, i32 1
//%14由%13得来,i8**变为i8*
%14 = load i8*, i8** %13, align 8, !invariant.load !32
//销毁函数,传入%14
%destroy = bitcast i8* %14 to void (%swift.opaque*, %swift.type*)*
call void %destroy(%swift.opaque* noalias %8, %swift.type* %T) #3
%15 = bitcast %swift.opaque* %8 to i8*
call void @llvm.lifetime.end.p0i8(i64 -1, i8* %15)
ret void
}
所以,泛型函数是如何管理内存的呢?
其实就是使用了Value Witness Table(VWT)
1.还原ValueWitnessTable
我们再来看看VWT
的数据结构
//i8*就是函数地址
%swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }
那么我们通过VWT
的数据结构来还原一下VWT
//%swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }
struct ValueWitnessTable {
var initializeBufferWithCopyOfBuffe: UnsafeRawPointer
//看到这里就能和我们之前IR代码里的逻辑对应上了
var destroy: UnsafeRawPointer
var initializeWithCopy: UnsafeRawPointer
var assignWithCopy: UnsafeRawPointer
var initializeWithTake: UnsafeRawPointer
var assignWithTake: UnsafeRawPointer
var getEnumTagSinglePayload: UnsafeRawPointer
var storeEnumTagSinglePayload: UnsafeRawPointer
var size: Int
var stride: Int
var flags: Int
}
struct LGTeacher {
var age = 10
}
struct TargetMetadata {
//这里就写第一个就行了,因为ValueWitnessTable在metadata内存地址的前8字节,所以后续的可以忽略
var kind: Int
}
let ptr = unsafeBitCast(LGTeacher.self as Any.Type, to: UnsafePointer.self)
let vwtPtr = UnsafeRawPointer(ptr).advanced(by: -MemoryLayout.size).assumingMemoryBound(to: UnsafePointer.self).pointee
print(vwtPtr.pointee.size) // 8,也就是结构体的大小
/*
可以通过cat address 还原unkown1~unkown8的函数名称,上面的我已经还原了
*/
记录在metadata内存地址的前8个字节,存放VWT
。来解决使用泛型时出现的内存分配问题
总结:
- 泛型是通过
VWT
(编译器生成的)管理内存的。 -
VWT
存储了size
、stride
、alignment
。换句话理解,当我们创建值类型数据时,编译器就是通过VWT
知道该分配多大的内存空间 - 值类型来说,通过源码中(
copy
、move
) 进行内存的拷贝 - 引用类型来说,
copy
引用计数加1
2.泛型参数传入闭包表达式
//如果函数中的泛型参数传入的是闭包呢?
//结构体{i8*, i64(捕获值)}
func makeIncreament() -> (Int) -> Int {
var runningTotal = 10
return {
runningTotal += $0
return runningTotal
}
}
func generic(t: T) {
}
var f = makeIncreament()
generic(t: f)
转化到IR代码分析
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = alloca %swift.function, align 8
%access-scratch = alloca [24 x i8], align 8
%3 = bitcast i8** %1 to i8*
//返回闭包结构体
%4 = call swiftcc { i8*, %swift.refcounted* } @"$s4main14makeIncreamentS2icyF"()
%5 = extractvalue { i8*, %swift.refcounted* } %4, 0
%6 = extractvalue { i8*, %swift.refcounted* } %4, 1
//s4main1fyS2icvp -> f变量
//存入函数地址及堆区空间到f
store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyS2icvp", i32 0, i32 0), align 8
store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyS2icvp", i32 0, i32 1), align 8
%7 = bitcast %swift.function* %2 to i8*
call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)
%8 = bitcast [24 x i8]* %access-scratch to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* %8)
call void @swift_beginAccess(i8* bitcast (%swift.function* @"$s4main1fyS2icvp" to i8*), [24 x i8]* %access-scratch, i64 32, i8* null) #2
//%9为从f取出的闭包函数地址
%9 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyS2icvp", i32 0, i32 0), align 8
//%10为从f取出的捕获值堆区空间
%10 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1fyS2icvp", i32 0, i32 1), align 8
%11 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %10) #2
call void @swift_endAccess([24 x i8]* %access-scratch) #2
%12 = bitcast [24 x i8]* %access-scratch to i8*
call void @llvm.lifetime.end.p0i8(i64 -1, i8* %12)
//创建堆区空间
%13 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 32, i64 7) #2
//强转至{ %swift.refcounted, %swift.function }
%14 = bitcast %swift.refcounted* %13 to <{ %swift.refcounted, %swift.function }>*
//取出%swift.function中i8*到%.fn
%15 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %14, i32 0, i32 1
%.fn = getelementptr inbounds %swift.function, %swift.function* %15, i32 0, i32 0
//将函数地址%9存入%.fn
store i8* %9, i8** %.fn, align 8
//取出%swift.function中堆区空间到%.fn
%.data = getelementptr inbounds %swift.function, %swift.function* %15, i32 0, i32 1
//存入%10到%.data
store %swift.refcounted* %10, %swift.refcounted** %.data, align 8
/*
看到这里,已经明白了逻辑。此时泛型参数为闭包表达式时
编译器会开辟一个堆空间(中间层){ %swift.refcounted, %swift.function }
将闭包表达式数据存入index 1
*/
//%2的index 0地址给%.fn1
%.fn1 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 0
//$sS2iIegyd_S2iIegnr_TRTA ---> partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@unowned Swift.Int)
//-> (@unowned Swift.Int) to @escaping @callee_guaranteed (@in_guaranteed Swift.Int) -> (@out Swift.Int)
//
//reabstraction 再抽象,存入的再抽象闭包地址
//也就是将闭包的地址传入%2的i8*中
store i8* bitcast (void (%TSi*, %TSi*, %swift.refcounted*)* @"$sS2iIegyd_S2iIegnr_TRTA" to i8*), i8** %.fn1, align 81
//将创建的堆区空间(中间层)存入%2中%swift.refcounted*
%.data2 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
store %swift.refcounted* %13, %swift.refcounted** %.data2, align 8
%16 = bitcast %swift.function* %2 to %swift.opaque*
%17 = call %swift.type* @__swift_instantiateConcreteTypeFromMangledName({ i32, i32 }* @"$sS2icMD") #9
//$s4main7generic1tyx_tlF ---> main.generic(t: A) -> ()
//执行函数generic,传入的是%16,也就是%2
call swiftcc void @"$s4main7generic1tyx_tlF"(%swift.opaque* noalias nocapture %16, %swift.type* %17)
/*
看到这里,结构信息就更加明确了
首先是一个再抽象的结构 { i8*, %swift_refcounted*}
其中的%swift_refcounted*存储的是我们开辟的中间层堆空间数据{ %swift.refcounted, %swift.function }
其中%swift.function就是闭包的数据结构
*/
%.data3 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
%18 = load %swift.refcounted*, %swift.refcounted** %.data3, align 8
call void @swift_release(%swift.refcounted* %18) #2
%19 = bitcast %swift.function* %2 to i8*
call void @llvm.lifetime.end.p0i8(i64 16, i8* %19)
ret i32 0
}
通过上面的IR代码逻辑,还原一下此时的闭包结构体的数据结构是怎样的
//如果函数中的泛型参数传入的是闭包呢?
//1.再抽象结构体 {i8*, %swift_refcounted*}
struct Reabstraction {
//再抽象函数地址
var ptr: UnsafeRawPointer
var function: UnsafePointer>
}
//2.中间层{ %swift_refcounted, %swift_function}
struct MiddleLayer {
var heapObject: HeapObject
var closureData: ClosureData
}
//3.闭包的结构体
struct ClosureData {
var ptr: UnsafeRawPointer
//如果传入的是函数的话,这里为nil。上面的ptr为函数地址
var captureValue: UnsafePointer
}
struct Box {
var heapObject: HeapObject
var captureValue: T
}
struct HeapObject {
var metadata: UnsafeRawPointer
var refCount: Int
}
//结构体{i8*, i64(捕获值)}
func makeIncreament() -> (Int) -> Int {
var runningTotal = 10
return {
runningTotal += $0
return runningTotal
}
}
func generic(t: T) {
let ptr = UnsafeMutablePointer.allocate(capacity: 1)
ptr.initialize(to: t)
defer {
ptr.deinitialize(count: 1)
ptr.deallocate()
}
let closurePtr = UnsafeRawPointer(ptr).assumingMemoryBound(to: Reabstraction>.self)
//再抽象闭包函数地址,闭包执行时调用的转发函数地址。先执行它再转发到闭包的函数地址
print(closurePtr.pointee.ptr) // 0x0000000100003b70
/*
❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100003b70
0000000100003b70 t _$sS2iIegyd_S2iIegnr_TRTA
❯ xcrun swift-demangle sS2iIegyd_S2iIegnr_TRTA
$sS2iIegyd_S2iIegnr_TRTA ---> partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@unowned Swift.Int) -> (@unowned Swift.Int) to @escaping @callee_guaranteed (@in_guaranteed Swift.Int) -> (@out Swift.Int)
*/
//闭包的函数地址
print(closurePtr.pointee.function.pointee.closureData.ptr) // 0x0000000100003c30
/*
❯ nm -p /Users/zt/Library/Developer/Xcode/DerivedData/swiftTest-hlhwnleuvbzgkogcaxdncrsezayx/Build/Products/Debug/swiftTest | grep 0000000100003c30
0000000100003c30 t _$s9swiftTest14makeIncreamentS2icyFS2icfU_TA
❯ xcrun swift-demangle s9swiftTest14makeIncreamentS2icyFS2icfU_TA
$s9swiftTest14makeIncreamentS2icyFS2icfU_TA ---> partial apply forwarder for closure #1 (Swift.Int) -> Swift.Int in swiftTest.makeIncreament() -> (Swift.Int) -> Swift.Int
*/
//拿到捕获的值
print(closurePtr.pointee.function.pointee.closureData.captureValue.pointee.captureValue) // 10
}
var f = makeIncreament()
generic(t: f)
//顺带回忆一下捕获多个值的时候的数据结构
//struct ClosureData {
// var ptr: UnsafeRawPointer
// var captureValue: UnsafePointer
//}
//
//struct Box {
// var heapObject: HeapObject
// var value: UnsafePointer>
// var value1: T2
//}
//
//struct Box1 {
// var heapObject: HeapObject
// var value: T
//}
至此,将闭包表达式传入泛型参数后,此时的数据结构已经还原出来了
那么我们思考一个问题,引入再抽象层和中间层的用意是什么呢?
因为我们可能传入的是闭包表达式
也有可能传入的是函数
。本质上传入函数或闭包表达式时,为了泛型管理统一,抽象了一层中间层来统一管理,捕获我们的函数或闭包表达式
四.Sequence&Collection
1.Sequence
对于Sequence
协议来说,表达的既可以是有限的集合,也可以是一个无限的集合,而它只需要提供集合中的元素和如何访问这些元素的接口即可。
我们在使用AnySequence
了解类型擦除的时候,其实已经对它有了一些了解。需要返回一个遵循IteratorProtocol
的迭代器。而迭代器负责实现next函数
来返回数据,当返回值为nil的时候表示迭代结束(有限的集合),否则会无限循环下去(也就是表示无限的集合)。
for-in
其实也是一个语法糖,其实也是使用迭代器来返回数据。当然这个可以通过SIL代码来观察
IteratorProtocol
源码
public protocol IteratorProtocol {
/// The type of element traversed by the iterator.
associatedtype Element
/// Advances to the next element and returns it, or `nil` if no next element
/// exists.
///
/// Repeatedly calling this method returns, in order, all the elements of the
/// underlying sequence. As soon as the sequence has run out of elements, all
/// subsequent calls return `nil`.
///
/// You must not call this method if any other copy of this iterator has been
/// advanced with a call to its `next()` method.
///
/// The following example shows how an iterator can be used explicitly to
/// emulate a `for`-`in` loop. First, retrieve a sequence's iterator, and
/// then call the iterator's `next()` method until it returns `nil`.
///
/// let numbers = [2, 3, 5, 7]
/// var numbersIterator = numbers.makeIterator()
///
/// while let num = numbersIterator.next() {
/// print(num)
/// }
/// // Prints "2"
/// // Prints "3"
/// // Prints "5"
/// // Prints "7"
///
/// - Returns: The next element in the underlying sequence, if a next element
/// exists; otherwise, `nil`.
mutating func next() -> Element?
}
Sequence
源码
public protocol Sequence {
/// A type representing the sequence's elements.
associatedtype Element
/// A type that provides the sequence's iteration interface and
/// encapsulates its iteration state.
associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
/// A type that represents a subsequence of some of the sequence's elements.
// associatedtype SubSequence: Sequence = AnySequence
// where Element == SubSequence.Element,
// SubSequence.SubSequence == SubSequence
// typealias SubSequence = AnySequence
/// Returns an iterator over the elements of this sequence.
__consuming func makeIterator() -> Iterator
... 下面的代码省略
}
使用Sequence
创建一个有限的集合
struct LGIterator: IteratorProtocol {
let sequence: LGSequence
var count = 0
init(_ sequence: LGSequence) {
self.sequence = sequence
}
//如果一直不返回nil,就会变成无限集合
mutating func next() -> Int? {
count += 1
if count > sequence.count {
return nil
}
return count
}
}
struct LGSequence: Sequence {
var count: Int
func makeIterator() -> LGIterator {
return LGIterator(self)
}
}
let sequence = LGSequence(count: 10)
for element in sequence {
print(element)
}
也可以使用IteratorProtocol
来表现一个无限的集合
struct Iterator: IteratorProtocol {
let value: Int
func next() -> Int? {
return value
}
}
let iterator = Iterator(value: 10)
while let value = iterator.next() {
print(value)
}
2.Collection
MutableCollection
允许集合通过下标改变自身元素
protocol MutableCollection : Collection {
subscript(i: Index) -> Iterator.Element {get set}
}
RangeReplaceableCollection
允许集合修改任意区间的元素
BidirectionalCollection
可以向前或向后遍历集合
RandomAccessCollection
任意访问集合元素。继承自BidirectionalCollection
3.通过Collection表达一个环形数组
通过环形数组
案例来了解MutableCollection
、RangeReplaceableCollection
、BidirectionalCollection
、RandomAccessCollection
//官方代码:返回2的下一次幂
extension FixedWidthInteger {
/// Returns the next power of two.
@inlinable
func nextPowerOf2() -> Self {
guard self != 0 else {
return 1
}
return 1 << (Self.bitWidth - (self - 1).leadingZeroBitCount)
}
}
/*
环形数组:顾名思义,数组是环形的。也就是甜甜圈形状,并且这个数组永远不会数组越界
头下标:负责记录取值的,每取一次值,下标值+1。永远指向第一个元素
尾下标:负责插入值,每插入一次值,下标值+1。永远指向第一个空闲空间的index
当下标超过数组长度后,进行模运算后,又会出现在数组的开头。形成一个闭环
*/
struct RingBuffer {
//ContiguousArray: Swift原生数组,更高效
//Array: 可以和OC交互的
var _buffer: ContiguousArray
var headIndex: Int = 0
var tailIndex: Int = 0
/*
例子: objc_msgSend中sel&mask = index
当一个数对另一个数取模时,例如:x % y。如果y的值是2^n时,此时我们可以使用&运算来代替模运算。
这中间其实就利用了2进制数的特性来完成的,2^n-1,二进制数n-1位全是1,进行&操作后刚好取到n-1的数据,此时的数据就是模
例如: x % 2^n = x & 2^n-1,使用&来代替模运算的高额开销,提高性能
*/
var mask: Int {
return _buffer.count - 1
}
init(capacity: Int) {
self._buffer = ContiguousArray(repeating: nil, count: capacity.nextPowerOf2())
}
mutating func appendValue(_ value: Element) {
_buffer[tailIndex] = value
indexAdvance(index: &tailIndex, by: 1)
}
mutating func read() -> Element? {
let element = _buffer[headIndex]
indexAdvance(index: &headIndex, by: 1)
return element
}
func indexAdvance( index: inout Int, by: Int) {
index = (index + by) & mask
}
}
//MutableCollection -> 可以通过下标修改集合数据
extension RingBuffer: Collection, MutableCollection {
/*
Collection 必须实现startIndex、endIndex、index()和subscript中的get
MutableCollection 必须实现subscript中的set来通过下标修改集合的元素
*/
var startIndex: Int {
return self.headIndex
}
var endIndex: Int {
return self.tailIndex
}
func index(after i: Int) -> Int {
return (i + 1) & mask
}
subscript(position: Int) -> Element? {
get {
return _buffer[position]
}
//MutableCollection
set {
_buffer[position] = newValue
}
}
}
//RangeReplaceableCollection -> 允许集合修改任意区间的元素
extension RingBuffer: RangeReplaceableCollection {
//必须要给一个初始化函数
init() {
self.init(capacity: 10)
}
/*
已经验证过了,但是init函数没有执行到。具体为什么要实现init函数暂时还不清楚
*/
mutating func remove(at i: Int) -> Element? {
var currentIndex = i
let element = _buffer[i]!
switch i {
/*
如果是头节点,直接将头节点移到下一位,同时将元素置为nil
*/
case headIndex:
indexAdvance(index: &headIndex, by: 1)
_buffer[i] = nil
default:
_buffer[i] = nil
var nextIndex = (i + 1) & mask
//这里实际上就是执行相邻元素互换,从i开始到tailIndex-1
while nextIndex != tailIndex {
_buffer.swapAt(currentIndex, nextIndex)
currentIndex = nextIndex
nextIndex = (currentIndex + 1) & mask
}
//删除了元素,尾节点向前移动一位
indexAdvance(index: &tailIndex, by: -1)
}
return element
}
/*
当然这里还有一些协议函数也可以去实现,例如:func removeFirst() -> Element?
*/
}
//RangeReplaceableCollection -> 可以向前或向后遍历集合
extension RingBuffer: BidirectionalCollection {
//返回下一个元素的下标
func index(before i: Int) -> Int {
//这里是向前
return (i - 1) & mask
}
}
//通过字面量数组创建
extension RingBuffer: ExpressibleByArrayLiteral {
init(arrayLiteral elements: Element...) {
self.init(capacity: elements.count)
for e in elements {
appendValue(e)
}
}
}
var ringBuffer: RingBuffer = RingBuffer(arrayLiteral: 0, 1, 2, 9, 4, 5)
ringBuffer.appendValue(6)
/*
此时当我们没有实现RandomAccessCollection时,也是可以使用index(0, offsetBy: 3)函数的
如果没有实现,那么执行的是编译器的默认实现,如果实现了执行实现的函数方法
*/
print(ringBuffer.index(0, offsetBy: 3)) // 3
/*
在这里的需求里,如果不实现RandomAccessCollection就会出问题
比如ringBuffer.index(0, offsetBy: 100)
*/
//print(ringBuffer.index(0, offsetBy: 100)) // 会报错,已经超出了EndIndex,数组越界
//RandomAccessCollection -> 任意访问集合元素
extension RingBuffer: RandomAccessCollection {
func distance(from start: Int, to end: Int) -> Int {
return start + end
}
func index(_ i: Int, offsetBy distance: Int) -> Int {
return (i + distance) & mask
}
}
/*
如果我们实现了RandomAccessCollection再来实现执行ringBuffer.index(0, offsetBy: 100)
此时就会走到我们自定义的逻辑,就不会再发生错误了
*/
print(ringBuffer.index(0, offsetBy: 100)) // 4
五.some关键字
Swift5.1
中引入了some
不透明类型来解决协议类型(泛型协议/含有Self)不能作为类型的问题。这个问题,我们在研究泛型的时候已经清楚了,主要是因为内存大小的分配问题。
//这样编译器就不会报错了,此时的userData就是一个遵守了DataFetch协议的不透明类型
let userData: some DataFetch = UserData()
一般与协议一起使用,some protocol
。表明这个类型是遵循协议的类型,具体类型并不知道。主要的好处就是隐藏了实际类型。
使用场景:
- 当我们在涉及底层库的时候,底层类型永远不会暴露给使用者,只能使用静态特性(也就是当前遵循的协议)。相当于把库的内部实现和外部使用分离开。
- 当我们需要使用泛型协议类型的时候,可以通过
some
使用。
//关于some,不透明类型介绍
protocol MyProtocol {
func test()
}
struct LGTeacher: MyProtocol {
func test() {
print("1")
}
}
/*
此时的some就是不透明类型,遵循MyProtocol协议的类型
*/
func makeStruct() -> some MyProtocol {
return LGTeacher()
}
//let t: some MyProtocol
let t = makeStruct()
t.test()
函数中返回值使用some
//返回不透明类型:Equatable
func makeA() -> some Equatable {
"A"
}
func makeB() -> some Equatable {
"B"
}
let a = makeA()
let a1 = makeA()
print(a == a1) // true
let b = makeB()
let b1 = makeB()
//此时的a和b就不是同一个类型了,编译器会认为是两个不透明类型不相等
//print(a == b) // Binary operator '==' cannot be applied to operands of type 'some Equatable' (result of 'makeA()') and 'some Equatable' (result of 'makeB()')