Swift ——泛型与集合
1. 为什么会有泛型
下面的 multiNumInt 是个非泛型函数,主要用于计算两个数的乘积
func multiNumInt(_ x: Int, _ y: Int) -> Int{
return x * y
}
multiNum 函数很实用,但是它只能用于 Int 值。如果我们想计算两个 Double 或者 其他类型的乘积的值,我们需要在写一些函数,比如 multiNumDouble函数:
func multiNumDouble(_ x: Double, _ y: Double) -> Double{
return x * y
}
但是我们发现, 函数体是一样的。唯一的区别是它 们接收值类型不同( Int和double)。
这个时候我们想找到一个可以计算任意类型值的函数怎么办?泛型正是能让我们写出这样函数的语法。
2. 泛型语法注意要点
我们先来看一下泛型的基本写法 ,首先我我们要指定一个占位符 T ,紧挨着写在函数名后面的 一对尖括号(当前我们这个 T 要遵循 FloatingPoint 协议,计算乘积所必须);其次我们就可以 使用 T 来替换任意定义的函数形式参数
func multiNum<T: FloatingPoint>(_ x: T, _ y: T) -> T{
return x * y
}
这是一个标准的非泛型版的数据结构,如果我们想要改造一下怎么做?
struct Stack{
private var items = [Int]()
mutating func push(_ item:Int) {
items.append(item)
}
mutating func pop(_ item:Int) -> Int? {
if items.isEmpty {return nil}
return items.removeLast()
}
}
改写成泛型后成为这样的结构体
struct Stack<T>{
private var items = [T]()
mutating func push(_ item:T) {
items.append(item)
}
mutating func pop(_ item:T) -> T? {
if items.isEmpty {return nil}
return items.removeLast()
}
}
基本上和栈的数据结构相关都有相同的操作,这个时候我们免不了抽取相同的行为定一个协议,这里我们定义协议的时候需要指明当前类型,那么能不能给 Protocol 也上一个泛 型?
protocol StackProtocol {
var itemCount: Int{ get }
mutating func pop() -> Int?
func index(of index: Int) -> Int
}
这样写就会出现一个错误,系统提示 Protocol 不支持泛型参数,需要我们使用关联类型来代替。
这样就可以了
那么在实现的时候,就可以让遵循结构的来指明这个关联类型的类型。
struct LGStack: StackProtocol{
typealias T = Int
private var items = [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]
}
}
同样的我们也可以给当前的关联类型添加约束,比如我们要求 T 必须都要遵循 FixWidthInteger,那么传入的T必须是遵循协议的类型,否则就会报错
我们也可以直接在约束中使用协议,在这个协议里, Even 是一个关联类型,就像上边例子中 StackProtocol 的 Item 类型一样。 Even 拥有两个约束:它必须遵循 EvenProtocol 协议(就是当前定义的协议),以及它的 T 类型必须是和容器里的 T 类型相同。 T 的约束是一个 where 分句。
protocol EvenProtocol: StackProtocol{
associatedtype Even: EvenProtocol where Even.Item == Item
func pushEven(_ item: Int) -> Even
}
在上面的例子中我们出现了一个 where 的协议,或者指定的类型形式参数和关联类型必须相同。泛型 Where 分句以 Where 关键字开 头,后接关联类型的约束或类型和关联类型一致的关系。
这个函数有两个形式参数,stack1 和 stack2。stack1 形式参数是 T1 类型,stack2 形式参 数是 T2 类型。T1 和 T2 是两个容器类型的类型形式参数,它们的类型在调用函数时决定。那么这个时候,传进来的泛型就需要满足三个条件
- 遵循StackProtocol协议
- T1 和T2类型相同
- T1的数据遵循Equatable
func compare<T1: StackProtocol, T2: StackProtocol>(_ stack1: T1, _ stack2:T2) -> Bool where T1.T == T2.T,T1.T:Equatable{
guard stack1.itemCount == stack2.itemCount else {
return false
}
for i in 0 ..< stack1.itemCount {
if stack1.index(of: Int(i)) != stack2.index(of: Int(i)) {
return false
}
}
return true
}
3. 类型擦除
前面我们已经了解了什么是协议,什么是泛型。接下来我们了解一下什么是类型擦除。首先我们 先来看一个例子(这里我们定义了一个泛型协议)
protocol DataFetch {
associatedtype dataType
func fetch(completion: ((Result<DataType, Error>) -> Void)?)
}
紧接着我们需要请求一个用户数据
struct UserData: DataFetch {
typealias dataType = User
func fetch(completion: ((Result<dataType, Error>) -> Void)?) {
let user = User(userId: 1001, name: "Kody")
completion?(.success(user))
}
}
这时候我们需要使用我们当前的 UserData 获取我们当前的 User 数据,运行一下,你会发现一个报错信息.也就意味着 DataFetch 只能用作泛型约束,不能用作具体类型!Swift是类型安全的,编译器无法确定dataType 的具体类型是什么,那么编译器就会报错
class ViewController {
let userdata:DataFetch
init(){
userdata.fetch() { (result) in
}
}
}