第二十二章 泛型
6. Extending a Generic Type (扩展泛型类型)
当我们扩展一个泛型类型的时候,并不需要提供类型参数列表作为扩展定义的一部分。 相反的,原类型定义中的类型参数在扩展体内依然是有效的,也就是说不需要我们我们在扩展定义里面在此定义类型参数,扩展体里的原类型参数可以作为当前扩展的类型参数使用。
下面这个例子是一个在泛型Stack类型上面扩展添加了一个只读的计算型属性topItem
,该属性会返回栈最顶端的那个元素而不是从栈里面移除。
extension Stack {
// 在当前的Stack里面扩展一个只读型属性topItem,
var topItem: Element? {
// 在这个扩展的属性体内进行了三元运算。
return items.isEmpty ? nil : items[items.count - 1]
}
}
这个属性topItem
返回的是一个类型为Element
的可选值。如果说这个栈是一个空白的那么该属性则返回nil
,如果栈不是空白而有值的情况下,该属性则会返回Items这个数组最后一个元素,栈是由一个叫Items的数组组成。
需要注意的是该扩展并没有定义类型参数列表,相反的是,Stack类型已有的类型参数名称 Element
,被用在扩展中来表示计算型属性topItem
的可选类型。现在这个计算行属性可以被Stack的任何一个实例所读取和查询最顶部的元素,而并不用从栈里面移除最顶部的元素。
// 假设这个栈不是空白的,那么则可以用可选绑定来读取作顶部的值
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// 输出:The top item on the stack is tres
7. Type Constraints (类型约束)
函数swapTwoValues(_:_:)
和类型Stack
可以作用于任何类型,然而,有些时候添加某些类型约束给泛型函数和泛型类型将会是非常有用的。类型约束指明了类型参数必须继承自某个特殊的类,或者遵循某个协议或协议组合。
举个例子,swift中的字典类型对字典的键(key
)作了些限制,详情见字典章节。字典类型的键必须要哈希化(hashable
),也就是说这个键要有一种方式来表达这个独一无二的键。字典则需要这些哈希化的键,以便于我们来查询某个键是否存在一个值,没有这个哈希化的请求,字典则不会知道是否应该插入或着替换某个键,也不会查询到某个值是否已经存在字典中。
类型约束会执行字典的键类型中的哈希化这个要求。指明了这个键类型必须遵循Hashable
协议, Hashable协议被定义在Swift的标准库里面了,swift所有的基本类型(String
,Int
,Double
,Bool
)在默认情况都会遵循Hashable协议。创建自定义泛型类型和定义类型约束的时候,这些约束将会提供泛型语言更加强大的功能,抽象概念,例如哈希化,描述的是类型在概念上的特征,而不是它们的显式类型。
7. 1 Type Constraints Syntax (类型约束语法)
通过在类型参数名称后面添加类或协议来写这个类型约束。要用逗号隔开,作为类型参数列表的一部分。泛型函数中类型约束的基本语法如下例所示(即使该语法和泛型类型的语法是相同的)
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里写的是函数体
}
上面的函数有两个类型参数,第一个类型参数T
有一个类型约束,那就是要求T必须是SomeClass
的子类,而第二个类型约束则是要求U
必须遵循SomeProtocol
这个协议。
7. 2 Type Constraints in Action (实践中的类型约束)
下面是一个非泛型函数findIndex(ofString:in:)
,该函数是在一个String类型数组里找到某个String
值。该函数返回的是一个可选的Int
值,该Int的值将会是String数组中的某一个元素的位置,如果说没有找到该元素或者返回nil
,则说明数组中找不到该String值。
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
该函数可以被用来在一个string值的数组中找出某个string值
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// 输出:The index of llama is 2
在数组中找到某个值的索引原理对于Strings来说并不实用,相反的我们通过将在类型T里提及到的String的值替换为与泛型相同的功能。
下面是我们期望写的函数findIndex(ofString:in:)
的泛型版本findIndex(of:in:)
。需要注意的是该函数返回类型依然是Int?
,因为函数返回的是一个可选的索引数字,而不是数组里面一个可选值(元素)。
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里面不是所有的类型都可以和相等运算符作比较的,如果我们自己写的结构体或着类用来表示一个复杂的数据模型(data model),举个例子,类或者结构体的相等关系不是swift语言能够为我们做推断或猜测的,就是因为这一点,就不能保证任何可能性的类型T
可以为这段代码做服务。所以当我们尝试编译这段代码的时候会出现报错。
swift的标准库里面定义了一个协议Equatable
,该协议请求任何一个已知的类型为该类的任何两个值来实现相等运算(==
)或者不等运算(!=
),所以swift标准的类型会自动支持该Equatable
协议。
所以该函数findIndex(of:in:)
现在可以很安全的使用该Equatable
协议,因为保证了这个相等运算操作,所以当我们定义函数的时候可以添加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
}
该函数findIndex(of:in:)
只有一个类型参数可以写成T: Equatable
,这就意味着任何类型T都会遵循Equatable
协议,现在该函数可以成功的进行编译,并且可以作用于任何符合Equatable
协议的类型,如Double或者String。
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