一、泛型解决的问题
首先来看一个实际开发中经常遇到的简单问题,这是一个标准的非泛型函数swapTwoInts(::),它可以交换两个Int值:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
该swapTwoInts(_:_:)
函数很有用,但它只能与Int值一起使用。如果要交换两个String值或两个Double值,则必须编写更多函数,例如下面的swapTwoStrings(_:_:)
和swapTwoDoubles(_:_:)
函数:
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
}
swapTwoInts(_:_:)
,swapTwoStrings(_:_:)
和swapTwoDoubles(_:_:)
三个函数体的功能是相同的。唯一区别是它们接收的参数类型分别不同。我们可以用泛型函数来解决这种因参数类型不同,而处理逻辑相同而导致编写大量重复代码的问题。
二、泛型函数
泛型函数可以使用任何类型。这是上面函数swapTwoInts(_:_:)
的通用版本,称为swapTwoValues(_:_:)
:
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"
上面的函数使用了一个称为T
的占位符类型名,而不是实际的类型名。占位符类型名没有指明T
必须是哪种类型,只是指明了参数a
和b
必须是同一类型T
。因为T
是占位符,所以Swift不会查找T
的实际类型。
三、类型参数
- 类型参数指定并命名占位符类型,将其写在函数名之后的一对尖括号(
<>
)内。 - 通过在尖括号(用逗号分隔)中写入多个类型参数名称,可以提供多个类型参数。
- 在大多数情况下,类型参数具有描述性名称,例如
Dictionary
中的<Key, Value>
和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()
}
}
该结构体使用一个名为items
的数组属性将值存储在堆栈中。Stack提供了两种方法,push以及pop在堆栈中压入和弹出值。这些方法被标记为mutating,因为它们需要修改(或改变)结构体的items
数组。
然而,上面显示的IntStack类型只能与Int值一起使用。定义一个泛型堆栈类会更实用,它可以管理任何类型值的堆栈。
下面再看一个泛型的堆栈示例:
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
let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings
五、扩展一个泛型类型
在扩展泛型类型时,不需要提供类型参数列表作为扩展定义的一部分。相反,来自原始类型定义的类型参数列表在扩展的主体中可用,原始类型参数名称用于引用来自原始定义的类型参数。
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."
六、类型约束
有时需要对泛型函数和泛型类型中使用的类型添加某些约束,使得只有满足该约束的类型才可以在泛型函数和泛型类型中使用。类型约束指定类型参数必须从特定的类继承,或符合特定的协议或协议组合。
(一)类型约束的语法
下面显示了泛型函数的类型约束的基本语法:
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
上面的假设函数有两个类型参数。第一个类型参数的约束是: T
必须是SomeClass
的子类。第二个类型参数的约束是,U
必须符合协议SomeProtocol
。
(二)类型约束的应用
首先来看一个示例,非泛型函数findIndex(ofString:in:)
的功能是在字符串数组array
中查找指定元素的索引值。
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"
上面的函数只适用于字符串类型。如果要适用于其他类型的数据,我们需要将其改成泛型函数,如下所示:
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
但上面的的函数无法编译。问题出在相等性检测if value == valueToFind
。在Swift语言中,不是每种数据类型都可以使用等号操作符(==)进行比较。只用遵守并实现了Equatable
协议的类型才可以使用==
操作符。Swift中的所有标准类型都自动支持Equatable
协议。
因此上面的函数的泛型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
关键字指定。
(一)关联类型应用
这是一个名为Container
的协议示例,它声明了一个名为Item
的关联类型:
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
下面的非泛型类IntStack
遵守并实现协议Container
的示例:
struct IntStack: Container {
// original IntStack implementation
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// conformance to the Container protocol
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
还可以使泛型Stack类型来遵守并实现Container协议:
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]
}
}
这次,类型参数Element
用作append(_:)
方法的item
参数的类型和下标的返回类型。因此,Swift可以推断出Element
是Item
用作这个特定容器的合适类型。
(二)扩展已有类型
- 我们不仅可以通过扩展,使现有类型遵守一个协议,还可以使其遵守一个关联类型的协议。
Swift的数组类型已经提供了一个append(_:)
方法、一个count
属性和一个带有Int索引的下标来检索其元素。这三个功能符合Container
协议的要求。这意味着可以通过声明Array
采用协议来扩展Array以符合Container
协议。可以使用一个空扩展来完成此操作,正如在声明采用带有扩展的协议时所述:
extension Array: Container {}
(三)向关联类型添加约束
- 可以向协议中的关联类型添加类型约束,以要求符合这些约束的类型满足这些约束。
下例中,Container
协议要求其中的元素类型要满足Equatable
协议:
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
(四)在关联类型的约束中使用协议
协议可以作为其自身需求的一部分出现。例如,这里有一个改进Container
协议的协议,添加了suffix(_:)
方法的需求。suffix(_:)
方法从容器末端返回给定数量的元素,并将它们存储在Suffix
类型的实例中。
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
在这个协议中,Suffix
是一个关联类型,就像上面Container
示例中的Item
类型一样。Suffix
有两个约束条件:
- 它必须符合
SuffixableContainer
协议; - 它的
Item
类型必须与容器的Item
类型相同;
Item
的约束是一个泛型的where语句。
下面是通过扩展,使Stack
类遵守并实现SuffixableContainer
协议的例子:
extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30
在上面示例中,Stack
的Suffix
关联类型也是Stack
,因此Stack
上的suffix
操作返回另一个Stack
。另外,遵守SuffixableContainer
协议的类型可以具有与其本身不同的Suffix
关联类型——即方法suffix
可以返回不同的类型。
例如,这里有一个非泛型IntStack
类型的扩展,它遵守SuffixableContainer
协议,使用Stack<Int>
作为关联类型,而不是IntStack
:
extension IntStack: SuffixableContainer {
func suffix(_ size: Int) -> Stack<Int> {
var result = Stack<Int>()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// Inferred that Suffix is Stack<Int>.
}
八、泛型where语句
- 泛型where语句让关联类型必须符合特定协议,或者特定类型参数和关联类型必须相同;
- 泛型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
}
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// Prints "All items match."
在上例中,要求函数的两个类型参数需要满足以下约束条件:
- C1必须符合
Container
协议(即C1: Container)。 - C2还必须符合
Container
协议(即C2: Container)。 - C1的
Item
必须与C2的Item
相同(即 C1.Item == C2.Item)。 - C1的
Item
必须符合Equatable
协议(即C1.Item: Equatable)。
而函数的两个参数someContainer
和anotherContainer
则需要满足以下约束: -
someContainer
是类型C1的容器。 -
anotherContainer
是C2类型的容器。 -
someContainer
和anotherContainer
中的元素类型相同。 -
someContainer
中的项可以使用不等运算符(!=)来检查它们是否不同。
九、泛型where分句的扩展
可以使用泛型where分句作为扩展的一部分。下面的示例扩展了前面示例中的泛型Stack
结构体,添加isTop(_:)
方法:
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// Prints "Top element is tres."
上例通过使用泛型where分句向扩展添加新的约束条件,只有当添加到栈中的item
符合Equatable
协议才添加isTop(_:)
方法。若向isTop(_:)
方法中传入的参数不符合Equatable
协议,则无法通过编译:
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error
还可以向协议的扩展使用泛型where分句。下例使用扩展向Container
协议添加了一个startsWith(_:)
方法:
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// Prints "Starts with something else."
上面示例中的泛型where分句要求Item
符合一个协议,但是也可以编写一个泛型where分句,要求Item
是特定类型。例如:
extension Container where Item == Double {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += self[index]
}
return sum / Double(count)
}
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// Prints "648.9"
十、泛型where分句的关联类型
可以在关联类型上包含泛型where分句。例如,创建一个包含迭代器的Container
实例:
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分句,可以向继承的关联类型添加约束。例如,下面的代码声明了一个 ComparableContainer
协议,它要求项目符合Comparable
:
protocol ComparableContainer: Container where Item: Comparable { }
十一、泛型下标
下标可以是泛型,也可以包括泛型的where分句。在subscript
后的尖括号中写上占位符类型名,然后在下标主体的右花括号前写上泛型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
}
}
网友评论