泛型(Generics)
泛型函数
泛型可以将类型参数化,提高代码复用率,减少代码量
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var n1 = 10
var n2 = 20
swapValues(&n1, &n2)
var d1 = 10.0
var d2 = 20.0
swapValues(&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 = 10
var i2 = 20
swapValues(&i1, &i2)
var d1 = 10.0
var d2 = 20.0
swapValues(&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() -> Element
func top() -> Element
func size() -> Int
}
定义一个类,遵守这个协议
// 关联类型设置成String
class MyStack : Stackable {
// 给关联类型设定真实类型(可以省略)
// typealias Element = String
var 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 = 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
}
}
类型约束
函数的泛型约束
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) -> Bool
where T1.Element == T2.Element, T2.Element : Hashable {
return true
}
*定义一个泛型函数,泛型遵守Stackable协议,如果想给Stackable中的Element添加约束,需要使用where添加其它约束
协议类型的注意点
定义一个有关联类型的协议:
protocol Runnable {
associatedtype Speed
var 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的真实类型。