1、泛型
- 泛型可以将类型参数化,提高代码抽象能力和复用性
1.1、泛型的存在意义
- 我们先来看一个非泛型的函数
这个函数计算了2个Int型参数的乘积,但是仅限于Int型,如果此时我们有需求计算Double型的乘积,难道要再写一个只有形参类型改成Double的函数吗?显然不划算,此时我们就需要使用泛型func multiNumInt(_ x: Int, _ y: Int) -> Int{ return x * y }
1.2、泛型语法书写
- 在函数名后面的一对尖括号中写入自定义的类型 T ,类型参数后面放置协议或者是类表示 类型约束(当前我们这个 T 要遵循 FloatingPoint 协议,计算乘积所必须);然后我们就可以使用 T 来替换任意定义的函数形式参数
//常规泛型 func multiNum<T: FloatingPoint>(_ x: T, _ y: T) -> T{ return x * y } //泛型栈结构 struct Stack<T>{ private var items = [T]() mutating func push(_ item: T){ items.append(item) } mutating func pop() -> T?{ if items.isEmpty { return nil } return items.removeLast() } }
1.3、关联类型
- 当我们遇到几个类、结构体等有相同的操作时,我们免不了抽取相同的行为定一个协议,但对于
协议是不支持泛型参数的声明的那么要使协议具有泛型的特性,需要使用 关联类型,并可给当前的关联类型添加约束,比如要求 Item 必须都要遵循
FixedWidthInteger;下边是将上边泛型栈结构进行提取形成的协议:关联类型 会使用protocol StackProtocol { associatedtype T:FixedWidthInteger var itemCount: T{ get } mutating func push(_item : T) mutating func pop() -> T? func index(of index: Int) -> T }associatedtype关键字给协议中用到的类型一个占位符名称(示例中为 T),协议定义中的相关类型我们都可以用这个占位符(T)替代,等到真正实现协议的时候在去确定当前占位符的类型struct LZStack:StackProtocol { typealias T = Int //将协议中的关联类型占位符赋予实际的类型 var items = [T]() //使用被赋予实际类型的T创建数据类型 var itemCount: Int { get { return items.count } } mutating func push(_ item: T) { items.append(item) } mutating func pop() -> T? { if items.isEmpty { return nil } return items.removeLast() } func index(of index: Int) -> T { return items[index] } } var s = LZStack() s.push(10) s.push(20) s.push(50) s.pop() print(s.items) //打印结果 [10, 20]
1.4、where
where语句主要用于表明 泛型需要满足的条件,即限制形式参数的要求protocol EvenProtocol: StackProtocol{ associatedtype Even: EvenProtocol where Even.T == T func pushEven(_ item: Int) -> Even }
1.5、类型擦除
-
为了了解什么是类型擦除,我们先准备一段根据不同业务类型,获取不同数据的代码
//获取数据泛型协议 protocol DataFetch { associatedtype dataType func fetch(completion: ((Result<dataType, Error>) -> Void)?) } //用户数据模型 struct User { let userId: Int let name: String } //获取一般用户数据 struct UserData: DataFetch { typealias dataType = User func fetch(completion: ((Result<dataType, Error>) -> Void)?) { let user = User(userId: 1001, name: "Normal") completion?(.success(user)) } } //获取VIP用户数据 struct VIPData: DataFetch { typealias dataType = User func fetch(completion: ((Result<dataType, Error>) -> Void)?) { let user = User(userId: 2001, name: "VIP") completion?(.success(user)) } }-
如果这时候我们需要在 controller 中使用 DataFetch 协议获取我们当前的 User 数据,运行后你会发现会报错,也就意味着 DataFetch协议只能用作泛型约束,不能用作具体类型!因为编译器无法确定 dataType 的具体类型是什么!
-
这时可能有人会说将 DataFetch协议改成具体 UserData 不就行了
这样行吗?确实能过编译,但是这会在 ViewController 和 UserData 对象之间创建一个依赖关系,增加里其中的耦合度,因此最好的方式是在引入一个
中间层
-
-
我们定义一个中间层结构体 AnyDataFetch , AnyDataFetch 实现 DataFetch 的所有方法。
//泛型协议 protocol DataFetch { associatedtype dataType func fetch(completion: ((Result<dataType, Error>) -> Void)?) } //用户数据 struct User { let userId: Int let name: String } //中间层结构体 struct AnyDataFetch<T>:DataFetch { typealias dataType = T private let _fetch:(((Result<dataType,Error>) -> Void)?) -> Void init<U: DataFetch>(_ fetchable: U) where U.dataType == T { //需要实现的类型在此处记录(依赖注入) _fetch = fetchable.fetch(completion:) } func fetch(completion: ((Result<T, Error>) -> Void)?) { //转发协议的抽象类型 _fetch(completion) } } struct UserData: DataFetch { typealias dataType = User func fetch(completion: ((Result<dataType, Error>) -> Void)?) { let user = User(userId: 1001, name: "LZ") completion?(.success(user)) } } struct VIPData: DataFetch { typealias dataType = User func fetch(completion: ((Result<dataType, Error>) -> Void)?) { let user = User(userId: 2001, name: "VIP") completion?(.success(user)) } } class someViewController { let userData : AnyDataFetch<User> init(_ data : AnyDataFetch<User>) { self.userData = data } } let userVip = VIPData() let anyFetchUser = AnyDataFetch<User>(userVip) let vc = someViewController.init(anyFetchUser) vc.userData.fetch { (result) in switch result { case .success(**let** user): print(user.name) case .failure(**let** error): print(error) } }- 在 AnyDataFetch 的初始化过程中,
实现协议的类型会被当做参数传入(依赖注入) - 在 AnyDataFetch 实现的具体协议方法 fetch 中,再
转发实现协议的抽象类型
- 在 AnyDataFetch 的初始化过程中,
-
对于 someViewController , 我接收的其实还是
AnyFetchable<User>类型,这其实就是所谓的 类型擦除;系统中的AnySequence,AnyCollection都是这样的原理
2、泛型原理
-
分析泛型原理需要查看底层 IR 代码,内容不过多赘述,关键的内容是下边部分:
// 这个 valueWitnesses 是一个值见证表 // valueWitnesses 记录了 size、stride、aligment 以及内存管理的一些函数。 %T.valueWitnesses = load i8**, i8*** %4, align 8, !invariant.load !15, !dereferenceable !16泛型函数是通过一个叫
ValueWitnessTable的东西来管理泛型函数的内存的;它的内存结构为:%swift.vwtable = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i64, i64, i32, i32 }这种 i8* 我们都可以把它当作 void*,也就是这些 i8* 其实就是当前所谓的函数
-
直接给出结论,ValueWitnessTable 的结构是:
struct ValueWitnessTable { var initializeBufferWithCopy: UnsafeRawPointer 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: UInt32 var extraInhabitantCount: UInt32 } -
当我们把闭包或者函数当作泛型参数进行传值的时候,它为了使泛型的管理统一,也是重新抽象了一层中间层来捕获当前传进来的函数;分辨当前传入的是闭包还是函数是通过 metadata 来进行判断的
3、AnySequence
- 假设你有这样一个需求,你需要迭代你的自定义属性 User ,其中 User 属性如下:
此刻对于 User 来说,我们应该要做的是实现 Sequence 协议struct User { var userId: Int var name: String }那这个时候,对于我们当前的另一个页面的VIP用户数据,也需要遍历,其中VIP的数据类型如下:struct User: Sequence { var userId: Int var name: String func makeIterator() -> CustomIterator { return CustomIterator(obj: self) } } struct CustomIterator: IteratorProtocol { var children: Mirror.Children init(obj: Persion) { children = Mirror(reflecting: obj).children } mutating func next() -> String? { guard let child = children.popFirst() else { return nil } return "\(child.label.wrapped) is \(child.value)" } }对于struct VIP { var vipdate: String var viplevel: Int var vipName: String }VIP和USer来说他们的行为是一致的,所以这里我们希望抽象出一个统一的协议此刻对于我们的struct CustomDataIterator: IteratorProtocol { var children: Mirror.Children init(obj: Any) { children = Mirror(reflecting: obj).children } mutating func next() -> String? { guard let child = children.popFirst() else { return nil } return "\(child.label.wrapped) is \(child.value)" } } protocol CustomDataSeuqence: Sequence { } extension CustomDataSeuqence { func makeIterator() -> CustomDataIterator { return CustomDataIterator(obj: self) } }VIP来说,只需要这样做接下来比如我们要做一个社群功能,需要当前同一个社群当中的用户进行遍历,如果是struct VIP: CustomDataSeuqence { var vipdate: String = "20221230" var viplevel: Int = 10 var vipName: String = "lg" }VIP,需 要特殊显示一些效果那么这里的for obj in Users { for userProperty in obj { print(userProperty) } }Users应该定义成什么类型呢?定义成Any类型,这个时候对于当前页面来说就需要将Any的类型转成CustomDataSeuqence,这样虽然可以,但是有点牵强,因为我们已经明确知道当前的类型如果是这样传值,编译器肯定会报错,因为当前编译器无法确定类型,这个时候我们就可以使用let users: [CustomDataSeuqence]AnySeuqence这个时候let user = User() let vip = VIP() let users: [AnySequence<String>] = [AnySequence(user), AnySequence(vip)] for user in users { // print(user) for item in user { print(item) } }AnySequence就将具体的Sequence类型隐藏了,调用者只知道数组中的元素是一个可以迭代输出字符串类型的序列