泛型(Generics)
泛型函数
泛型可以将类型参数化,提高代码复用率,减少代码量
func swapValues<T>(_ a: inout T, _ b: inout T) {(a, b) = (b, a)}var n1 = 10var n2 = 20swapValues(&n1, &n2)var d1 = 10.0var d2 = 20.0swapValues(&d1, &d2)
泛型函数赋值给变量:
func test<T1, T2>(_ a: T1, _ b: T2) {}var fn: (Int, Double) -> () = test
泛型类型
定义一个泛型栈(类):
class Stack<E> {var elements = [E]()func push(_ element: E) {elements.append(element)}func pop() -> E {elements.removeLast()}func top() -> E {elements.last!}func size() -> Int {elements.count}}var intStack = Stack<Int>()var stringStack = Stack<String>()var anyStack = Stack<Any>()
*如果初始化器里明确了E的类型,可以不用写<类型>
泛型类的继承:
// 继承时需要书写泛型class SubStack<E> : Stack<E> {}
定义一个泛型栈(结构体):
struct Stack<E> {var elements = [E]()mutating func push(_ element: E) {elements.append(element)}mutating func pop() -> E {elements.removeLast()}func top() -> E {elements.last!}func size() -> Int {elements.count}}
*需要给push和pop函数添加mutating修饰,因为这两个方法会修改结构体内存中的数据
定义一个泛型枚举:
enum MyError<T> {case netError(T)case dataError(String)}let error0 = MyError<Int>.netError(404)let error1 = MyError.netError(110)let error2 = MyError.netError("net work off")let error3 = MyError<Int>.dataError("no data")
*error3也需要加上
,要明确枚举的数据类型。
泛型的本质
func swapValues<T>(_ a: inout T, _ b: inout T) {(a, b) = (b, a)print(a, b)}var i1 = 10var i2 = 20swapValues(&i1, &i2)var d1 = 10.0var d2 = 20.0swapValues(&d1, &d2)
查看汇编代码,观察两次泛型函数的调用,调用的函数地址相同,可以看出泛型并不是根据类型创建多个函数进行调用。
callq 0x100003fa0 ; SwiftTest.swapValues<T>(inout T, inout T) -> () at main.swift:1422... ...callq 0x100003fa0 ; SwiftTest.swapValues<T>(inout T, inout T) -> () at main.swift:1422
观察其他汇编代码,可以看到拿到了Int和Double的元信息,可以推断出泛型函数是拿到了泛型的元信息进行调用处理的。
movq 0x417b(%rip), %rdx ; (void *)0x00007ff8434c4c20: type metadata for Swift.Int... ...movq 0x40f5(%rip), %rdx ; (void *)0x00007ff8434c4880: type metadata for Swift.Double
关联类型(Associated Type)
关联类型的作用:给协议中用到的类型定义一个占位名称
协议中可以拥有多个关联类型(可以理解为协议中的泛型,可以定义多个)
// 定义一个栈的协议protocol Stackable {associatedtype Element // 关联类型mutating func push(_ element: Element)mutating func pop() -> Elementfunc top() -> Elementfunc size() -> Int}
定义一个类,遵守这个协议
// 关联类型设置成Stringclass MyStack : Stackable {// 给关联类型设定真实类型(可以省略)// typealias Element = Stringvar elements = [String]()func push(_ element: String) {elements.append(element)}func pop() -> String {elements.removeLast()}func top() -> String {elements.last!}func size() -> Int {elements.count}}
定义一个泛型类,遵守这个协议
class Stack<E> : Stackable {// typealias Element = Evar elements = [E]()func push(_ element: E) {elements.append(element)}func pop() -> E {elements.removeLast()}func top() -> E {elements.last!}func size() -> Int {elements.count}}
类型约束
函数的泛型约束
protocol Play {}class Person {}// 定义一个泛型T,必须是Person类,且遵守Play协议func game<T : Person & Play> (aPerson: T) {}
协议的类型约束:
// 定义一个协议,有一个遵守Equatable协议的关联类型protocol Stackable {associatedtype Element: Equatable}// 定义一个泛型类,遵守了Stackable协议,那么他的泛型也需要遵守Equatable协议class Stack<E : Equatable> : Stackable {typealias Element = E}
对关联类型的约束
func equal<T1: Stackable, T2: Stackable>(s1: T1, s2: T2) -> Boolwhere T1.Element == T2.Element, T2.Element : Hashable {return true}
*定义一个泛型函数,泛型遵守Stackable协议,如果想给Stackable中的Element添加约束,需要使用where添加其它约束
协议类型的注意点
定义一个有关联类型的协议:
protocol Runnable {associatedtype Speedvar speed: Speed { get }}class Person : Runnable {var speed: Double { 0.0 }}class Car : Runnable {var speed: Int { 0 }}func get(_ type: Int) -> Runnable {if type == 0 {return Person()}return Car()}var r1 = get(0)var r2 = get(1)

报错原因是编译时期无法确认associatedtype的类型
解决方案一:泛型方案
// 定义泛型函数,确认调用时能够确定协议类型func get<T: Runnable>(_ type: Int) -> T {if type == 0 {return Person() as! T}return Car() as! T}// : Person 代表传入的协议类型是Person类型var r1: Person = get(0)// : Car 代表传入的协议类型是Car类型var r2: Car = get(1)
解决方案二:不透明类型(Opaque Type)
使用some关键字声明一个不透明类型
func get(_ type: Int) -> some Runnable {return Car()}var r1 = get(0)var r2 = get(1)
some用途
1、some用于返回值
如果想返回一个遵守某种协议的对象,但是对象的具体类型不想让外部知道,保证对象暴露的接口只有协议里的接口。
2、some一般还可以用在属性类型上
protocol Runnable {associatedtype Speed}class Dog : Runnable {typealias Speed = Double}class Person {// person 的宠物var pet: some Runnable {return Dog()}}
只想暴露pet遵守Runnable协议,不需要暴露pet的真实类型。
