有两种范型,即范型函数和范型类型。可以对范型类型可以添加各种限制,以限制范型的作用范围。如 ”T: Equatable“,意在范型 T 需要遵从 Equatable 协议。
协议不支持范型,导致其作用类型无法参数化,限制范型编程的能力,为此 Swift 引入了关联类型。协议中的关联类型等价于类、结构体和枚举中的范型类型,都是占位符。
范型 Where 语句只作用于程序的当前元素上,如扩展、类型、枚举、函数等,多个元素的多个Where** 语句保持独立。**
范****型是对参数的类型参数化的一种机制。使用范型可以写出高度灵活、可重用的函数与类型。
范型函数
代码冗余
考虑下面代码块:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwo* 函数使用 in-out 参数,可参考 In-Out Parameters ,这三个函数的执行过程完全一样,只是操作的类型实例不一样,完全可以把类型参数化,合并三个函数为一个函数。
范型函数
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
类型参数
在 swapTwoValues(_:_:) 中,占位符 T 就是一个类型参数。
通常用描述性的名字命名类型参数,如 Key 和Value 用于Dictionary<Key, Value> 中, Element 用于 Array<Element> 中,用来显示范型或函数,于类型参数之间的关系。如果没有有意义的关系,可以使用单个字母,如T``U 和 V。
范型类型
Swift 有范型函数和范型类型。下面展示如何定义一个范型类型---栈。

非范型版本的栈:
struct IntStack {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
mutating关键字可参考juejin.cn/post/687009… 协议
范型版本的栈:
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings
范型类型扩展
在扩展范型类型时,不会提供类型参数,类型参数是自动可获取的:
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
范型类型限制
用来限制类型参数必须是某类的子类,或实现某协议或协议组合。如 Dictionary 类型的 key 必须实现 Hashable 协议 Conforming to the Hashable Protocol 。
类型 T 必须是 SomeClass 的子类;U 必须遵从协议 SomeProtocol 。
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
下面来看一个例子:
findIndex 查找元素 valueToFind 在数组 array 中的下标,该函数可以编译运行:
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
把 String 范型化,得到如下范型函数。但是在 Swift 中并不是所有的类型都支持操作符 (==),没有对 T 做任何的限制是无法编译的。
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
在限定 T 遵从 Equatable 协议后,可编译运行:
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2
关联类型
协议不支持范型参数。在定义协议时,可使用关键字 associatedtype 声明几个关联类型,作为类、结构或枚举的范型参数占位符。
The actual type to use for that associated type isn’t specified until the protocol is adopted.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
struct Stack<Element>: Container {
// original Stack<Element> implementation
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
绑定范型类型和关联类型
类型中可以使用范型如定义 T,但协议中无法直接使用范型。在范型类型定义或是扩展中,可以把协议中的关联类型和范型类型中的类型参数绑定在一起。
protocol LinkProtocol { associatedtype Item mutating func append(_ item: Item)
}class LinkedClass<T> { }extension LinkedClass: LinkProtocol { typealias Item = T;//绑定范型参数 T 和关联类型 Item func append(_ item: T) { //...
}}
如果范型类型已经实现了扩展协议中所有的限制(关联类型的命名可随意),写一个空扩展即可,系统会自动推断出关联类型的类型
protocol LinkProtocol { associatedtype Item mutating func append(_ item: Item)}class LinkedClass<T> { func append(_ item: T) { //...}}extension LinkedClass: LinkProtocol {}
给关联类型添加限制
可以给协议中的关联类型添加限制:
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
在关联类型的限制中使用协议
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
在协议 SuffixableContainer 的声明中,可以使用声明的协议限制关联类型,并要求继承的 Container 协议中的 Item 类型和关联类型的属性的 Item 的类型相同。
protocol Container { associatedtype Item: Equatable func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get }}protocol SuffixableContainer: Container { associatedtype Suffix: SuffixableContainer where Suffix.Item == Item func suffix(_ size: Int) -> Suffix //returns a given number of elements}class Stack<Element: Equatable>: Container, SuffixableContainer { var items = [Element]() func push(_ item: Element) { items.append(item) } func pop() -> Element { return items.removeLast() } func append(_ item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element { return items[i] } func suffix(_ size: Int) -> Stack { let result = Stack() for index in (count-size)..<count { result.append(self[index]) } return result }}
这里用 class 替换 struct,并省去了 mutating 声明。可参考juejin.cn/post/687009… 中的 “值类型的可变实例方法”
范型 Where 语句
可以给类型参数和关联类型加上限制也可以通过 Where 语句添加限制,如遵从某个协议、类型参数与关联类型必须等价等。
范型函数 Where 语句
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable { // Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
} // Check each pair of items to see if they're equivalent.
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
} // All items match, so return true.
return true
}
C1must conform to theContainerprotocol (written asC1: Container).C2must also conform to theContainerprotocol (written asC2: Container).- The
ItemforC1must be the same as theItemforC2(written asC1.Item == C2.Item). - The
ItemforC1must conform to theEquatableprotocol (written asC1.Item: Equatable).
扩展 Where 语句
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
上下文 Where 子句
范型的 Where 子句可作用于整个元素,如类、结构体、枚举、协议或是扩展,也可以作用于其内部的某个限制上,多个限制的 Where 限制相互独立。如下面 where Item == Int 限制作用于函数 func average() -> Double;where Item: Equatable 作用于函数 func endsWith(_ item: Item) -> Bool。
extension Container {
func average() -> Double where Item == Int {
var sum = 0.0
for index in 0..<count {
sum += Double(self[index])
}
return sum / Double(count)
}
func endsWith(_ item: Item) -> Bool where Item: Equatable {
return count >= 1 && self[count-1] == item
}
}
let numbers = [1260, 1200, 98, 37]
print(numbers.average())
// Prints "648.75"
print(numbers.endsWith(37))
// Prints "true"
下面是非上下文限制构造方式:
extension Container where Item == Int {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += Double(self[index])
}
return sum / Double(count)
}
}
extension Container where Item: Equatable {
func endsWith(_ item: Item) -> Bool {
return count >= 1 && self[count-1] == item
}
}
带有范型 Where 语句的关联类型
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
func makeIterator() -> Iterator
}
范型下标
可以在下标的返回值和参数中使用范型,并用范型 where 语句添加限制。
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result = [Item]()
for index in indices {
result.append(self[index])
}
return result
}
}
Container 协议的扩展向 Container 添加了一个下标,会以索引序列 Indices: Sequence 为顺序,以数组的形式输出容器内的元素。 该范型下标涉及的限制有:
- <Indices: Sequence>:范型参数
Indices是一个Sequence - (indices: Indices):该范型下标只有一个参数,即
indices - where Indices.Iterator.Element == Int:范型
where语句要求序列的迭代器的操作的元素是 Int 类型。即该范型下标操作的元素类型是 Int。