泛型函数
- 泛型可以将类型参数化,提高代码复用率,减少代码量
看个例子,交换两个变量的值:定义一个函数,参数为inout
参数,内部元组实现交换两个外部传入的变量
func swapValue(_ a: inout Int, _ b: inout Int) {
(a, b) = (b, a)
}
var n1 = 10
var n2 = 20
swapValue(&n1, &n2)
print(n1, n2) // 20 10
上面的swapValue
函数的参数只支持Int
类型,用局限性,如果要支持其他类型的参数,就必须写多个函数,很麻烦,严重增加代码量,这时候就需要参数支持泛型,能传入任意类型的参数。
这里的T
代表一种不确定的类型:(这里的T
随便自定义,通常用T
,代表type
)
func swapValue(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var n1 = "abc"
var n2 = "123"
swapValue(&n1, &n2)
print(n1, n2) //123 abc
var n1 = true
var n2 = false
swapValue(&n1, &n2)
print(n1, n2) //false true
var n1 = 10.1
var n2 = 90.23
swapValue(&n1, &n2)
print(n1, n2) //90.23 10.1
其实swift
内部就已经提供了一个swap
交换的方法,我们点进去看下就可以知道我们自定义的swapValue
跟它的定义方法是一样的:
/// Exchanges the values of the two arguments.
///
/// The two arguments must not alias each other. To swap two elements of a
/// mutable collection, use the `swapAt(_:_:)` method of that collection
/// instead of this function.
///
/// - Parameters:
/// - a: The first value to swap.
/// - b: The second value to swap.
@inlinable public func swap(_ a: inout T, _ b: inout T)
- 泛型函数赋值给变量
var n1 = 10
var n2 = 20
var fn: (inout Int, inout Int) -> () = swapValue
fn(&n1, &n2)
func test(_ t1: T1, _ t2: T2) { }
var fn: (Int, Double) -> () = test
泛型类型
结构体、类、枚举也可以增加泛型定义
举个例子,利用数组实现一个栈,类Stack
后面加个
,代表支持任意类型的元素操作
class Stack {
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的栈
存在继承的写法:
class SubStack: Stack {
}
struct
中自定义需要加上mutating
struct Stack {
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 }
}
枚举中定义泛型
enum Score {
case point(T)
case grade(String)
}
let score0 = Score.point(10)
let score1 = Score.point(10)
let score2 = Score.point(10.1)
let score3 = Score.grade("C")
关联类型 Associated Type
- 给协议中用到的类型定义一个占位名称
定义一个协议Stackable
,声明几个栈的常规操作,如果要实现一个栈,遵循Stackable
协议,内部自己去实现
protocol Stackable {
associatedtype Element //关联类型(可以理解泛型)
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
协议中实现泛型无法像类、结构体、枚举那样,只能用associatedtype
protocol Stackable { }
//报错:Protocols do not allow generic parameters; use associated types instead
- 协议中可以拥有多个关联类型
protocol Stackable {
associatedtype Element //关联类型
associatedtype Element2 //关联类型
associatedtype Element3 //关联类型
}
具体应用中的实现:
声明一个类StringStack
,遵循Stackable
协议,设置真实关联类型为String
类型。
class StringStack: Stackable {
//给关联类型设置真实类型
typealias Element = String
//...
}
因为编译器会自动处理,其实可以不用写明typealias Element = String
,只要实现协议方法的时候写明是String
就行,编译器会自动关联到需要的类型
class StringStack: 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
的时候设置泛型:
class Stack: 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 }
}
类型约束
下面的代码中,对于泛型T
设置了一些约束,参数泛型T
必须是Person
类或者子类且遵循了Runnable
协议的。
protocol Runnable { }
class Person { }
func swapValues(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
下面的代码中,协议里面的关联类型遵守Equatable
协议,那么遵守Stackable
协议的类的泛型也必须遵守Equatable
协议。
protocol Stackable {
associatedtype Element: Equatable
}
class Stack: Stackable {
typealias Element = E
}
下面的代码中,S1
、S2
必须遵守Stackable
协议,且S1.Element == S2.Element
,同时S1.Element
要遵守Hashable
协议。
func equal(_ s1: S1, _ s2: S2) -> Bool
where S1.Element == S2.Element, S1.Element: Hashable {
return false
}
var s1 = Stack()
var s2 = Stack()
var s3 = Stack()
equal(s1, s2) //编译正确
equal(s1, s3) //编译错误:Global function 'equal' requires the types 'Stack.Element' (aka 'Int') and 'Stack.Element' (aka 'String') be equivalent
协议类型的注意点
定义Runnable
协议,定义类Person
、Car
遵守Runnable
协议。
可以看到变量r1
是Person
对象,r2
是Car
对象。
但是编译器并不知道r1
、r2
具体哪个是Person
,哪个是Car
,因为get
返回值统一是Runnable
类型
protocol Runnable { }
class Person: Runnable { }
class Car: Runnable { }
func get(_ type: Int) -> Runnable {
if type == 0 {
return Person()
}
return Car()
}
var r1 = get(0)
var r2 = get(1)
接下来给协议Runnable
引入关联类型associatedtype
:声明关联类型Speed
,有一个speed
只读的计算属性
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) -> T {
if type == 0 {
return Person() as! T
}
return Car() as! T
}
var r1: Person = get(0)
var r2: Car = get(1)
- 用不透明类型
some
解决问题
func get(_ type: Int) -> some Runnable {
return Car()
}
some
限制只能返回一种类型,因此编译器能确定最后返回的是什么类型,编译就能通过。