泛型(Generics)

泛型函数

泛型可以将类型参数化,提高代码复用率,减少代码量

  1. func swapValues<T>(_ a: inout T, _ b: inout T) {
  2. (a, b) = (b, a)
  3. }
  4. var n1 = 10
  5. var n2 = 20
  6. swapValues(&n1, &n2)
  7. var d1 = 10.0
  8. var d2 = 20.0
  9. swapValues(&d1, &d2)

泛型函数赋值给变量:

  1. func test<T1, T2>(_ a: T1, _ b: T2) {}
  2. var fn: (Int, Double) -> () = test

泛型类型

定义一个泛型栈(类):

  1. class Stack<E> {
  2. var elements = [E]()
  3. func push(_ element: E) {
  4. elements.append(element)
  5. }
  6. func pop() -> E {
  7. elements.removeLast()
  8. }
  9. func top() -> E {
  10. elements.last!
  11. }
  12. func size() -> Int {
  13. elements.count
  14. }
  15. }
  16. var intStack = Stack<Int>()
  17. var stringStack = Stack<String>()
  18. var anyStack = Stack<Any>()

*如果初始化器里明确了E的类型,可以不用写<类型>

泛型类的继承:

  1. // 继承时需要书写泛型
  2. class SubStack<E> : Stack<E> {
  3. }

定义一个泛型栈(结构体):

  1. struct Stack<E> {
  2. var elements = [E]()
  3. mutating func push(_ element: E) {
  4. elements.append(element)
  5. }
  6. mutating func pop() -> E {
  7. elements.removeLast()
  8. }
  9. func top() -> E {
  10. elements.last!
  11. }
  12. func size() -> Int {
  13. elements.count
  14. }
  15. }

*需要给push和pop函数添加mutating修饰,因为这两个方法会修改结构体内存中的数据

定义一个泛型枚举:

  1. enum MyError<T> {
  2. case netError(T)
  3. case dataError(String)
  4. }
  5. let error0 = MyError<Int>.netError(404)
  6. let error1 = MyError.netError(110)
  7. let error2 = MyError.netError("net work off")
  8. let error3 = MyError<Int>.dataError("no data")

*error3也需要加上,要明确枚举的数据类型。

泛型的本质

  1. func swapValues<T>(_ a: inout T, _ b: inout T) {
  2. (a, b) = (b, a)
  3. print(a, b)
  4. }
  5. var i1 = 10
  6. var i2 = 20
  7. swapValues(&i1, &i2)
  8. var d1 = 10.0
  9. var d2 = 20.0
  10. swapValues(&d1, &d2)

查看汇编代码,观察两次泛型函数的调用,调用的函数地址相同,可以看出泛型并不是根据类型创建多个函数进行调用。

  1. callq 0x100003fa0 ; SwiftTest.swapValues<T>(inout T, inout T) -> () at main.swift:1422
  2. ... ...
  3. callq 0x100003fa0 ; SwiftTest.swapValues<T>(inout T, inout T) -> () at main.swift:1422

观察其他汇编代码,可以看到拿到了Int和Double的元信息,可以推断出泛型函数是拿到了泛型的元信息进行调用处理的。

  1. movq 0x417b(%rip), %rdx ; (void *)0x00007ff8434c4c20: type metadata for Swift.Int
  2. ... ...
  3. movq 0x40f5(%rip), %rdx ; (void *)0x00007ff8434c4880: type metadata for Swift.Double

关联类型(Associated Type)

关联类型的作用:给协议中用到的类型定义一个占位名称
协议中可以拥有多个关联类型(可以理解为协议中的泛型,可以定义多个)

  1. // 定义一个栈的协议
  2. protocol Stackable {
  3. associatedtype Element // 关联类型
  4. mutating func push(_ element: Element)
  5. mutating func pop() -> Element
  6. func top() -> Element
  7. func size() -> Int
  8. }

定义一个类,遵守这个协议

  1. // 关联类型设置成String
  2. class MyStack : Stackable {
  3. // 给关联类型设定真实类型(可以省略)
  4. // typealias Element = String
  5. var elements = [String]()
  6. func push(_ element: String) {
  7. elements.append(element)
  8. }
  9. func pop() -> String {
  10. elements.removeLast()
  11. }
  12. func top() -> String {
  13. elements.last!
  14. }
  15. func size() -> Int {
  16. elements.count
  17. }
  18. }

定义一个泛型类,遵守这个协议

  1. class Stack<E> : Stackable {
  2. // typealias Element = E
  3. var elements = [E]()
  4. func push(_ element: E) {
  5. elements.append(element)
  6. }
  7. func pop() -> E {
  8. elements.removeLast()
  9. }
  10. func top() -> E {
  11. elements.last!
  12. }
  13. func size() -> Int {
  14. elements.count
  15. }
  16. }

类型约束

函数的泛型约束

  1. protocol Play {}
  2. class Person {}
  3. // 定义一个泛型T,必须是Person类,且遵守Play协议
  4. func game<T : Person & Play> (aPerson: T) {
  5. }

协议的类型约束:

  1. // 定义一个协议,有一个遵守Equatable协议的关联类型
  2. protocol Stackable {
  3. associatedtype Element: Equatable
  4. }
  5. // 定义一个泛型类,遵守了Stackable协议,那么他的泛型也需要遵守Equatable协议
  6. class Stack<E : Equatable> : Stackable {
  7. typealias Element = E
  8. }

对关联类型的约束

  1. func equal<T1: Stackable, T2: Stackable>(s1: T1, s2: T2) -> Bool
  2. where T1.Element == T2.Element, T2.Element : Hashable {
  3. return true
  4. }

*定义一个泛型函数,泛型遵守Stackable协议,如果想给Stackable中的Element添加约束,需要使用where添加其它约束

协议类型的注意点

定义一个有关联类型的协议:

  1. protocol Runnable {
  2. associatedtype Speed
  3. var speed: Speed { get }
  4. }
  5. class Person : Runnable {
  6. var speed: Double { 0.0 }
  7. }
  8. class Car : Runnable {
  9. var speed: Int { 0 }
  10. }
  11. func get(_ type: Int) -> Runnable {
  12. if type == 0 {
  13. return Person()
  14. }
  15. return Car()
  16. }
  17. var r1 = get(0)
  18. var r2 = get(1)

image.png
报错原因是编译时期无法确认associatedtype的类型

解决方案一:泛型方案

  1. // 定义泛型函数,确认调用时能够确定协议类型
  2. func get<T: Runnable>(_ type: Int) -> T {
  3. if type == 0 {
  4. return Person() as! T
  5. }
  6. return Car() as! T
  7. }
  8. // : Person 代表传入的协议类型是Person类型
  9. var r1: Person = get(0)
  10. // : Car 代表传入的协议类型是Car类型
  11. var r2: Car = get(1)

解决方案二:不透明类型(Opaque Type)

使用some关键字声明一个不透明类型

  1. func get(_ type: Int) -> some Runnable {
  2. return Car()
  3. }
  4. var r1 = get(0)
  5. var r2 = get(1)

some限制只能返回一种类型
image.png

some用途

1、some用于返回值

如果想返回一个遵守某种协议的对象,但是对象的具体类型不想让外部知道,保证对象暴露的接口只有协议里的接口。

2、some一般还可以用在属性类型上

  1. protocol Runnable {
  2. associatedtype Speed
  3. }
  4. class Dog : Runnable {
  5. typealias Speed = Double
  6. }
  7. class Person {
  8. // person 的宠物
  9. var pet: some Runnable {
  10. return Dog()
  11. }
  12. }

只想暴露pet遵守Runnable协议,不需要暴露pet的真实类型。