学习Swift -- 泛型

时间:2021-06-23 09:14:24

泛型

泛型代码可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。

泛型所解决的问题

先来看一个交换两个int值的例子:

var number1 = 5
var number2 = 10 func swapTwoInts(inout a: Int, inout _ b: Int) {
let temp = a
a = b
b = temp
} swapTwoInts(&number1, &number2)

因为Int是值类型,必须使用inout关键字(输入输出参数)来保证两个值的交换,但是这个方法只能交换两个Int值,如果想要实现两个Double,String就不得不声明另两个方法,但是两个方法内部实现会与这个方法内部相同,况且再生成两个方法或多个会很耗时。所以我们用泛型来解决这个问题。

泛型函数

var number1 = 5
var number2 = 10 var string1 = "string1"
var string2 = "string2" var double1 = 3.5
var double2 = 18.3 func swapTwoValues<T>(inout a: T, inout _ b: T) {
let temp = a
a = b
b = temp
} swapTwoValues(&number1, &number2)
swapTwoValues(&string1, &string2)
swapTwoValues(&double1, &double2)

这是泛型函数,适用于任何类型,这样可以解决重复代码的问题。写法是:func 函数名<泛型名>(参数名:泛型名) { 函数体 }

注意:swapTwoValues方法是模仿Swift标准库中的swap函数,如果以后想要实现两个值交换,可以使用swap函数。

多个泛型名:

let dic = ["key1" : 33, "key2" : 44.8, "key3" : "Name"]

func printDictionary<Key, Value>(key key: Key, value: Value) {
// 命名泛型时遵循驼峰式命名
print("键是\(key), 值是\(value)")
} for (key, value) in dic {
printDictionary(key: key, value: value)
}
//键是key1, 值是33
//键是key3, 值是Name
//键是key2, 值是44.8

泛型类型

先来看一个非泛型的类型,一个Int型的栈实例:

struct IntStack {
var items = [Int]() mutating func push(item: Int) {
items.append(item)
} mutating func pop() -> Int {
return items.removeLast()
}
} var stack = IntStack(items: [11, 5, 38])
let last = stack.pop()
print(last) // 38 stack.push(150)

可以看到上面的例子是一个栈的结构(先进后出),但是IntStack类型内的属性和接口只适用于Int,下面来实现一个泛型的栈:

struct Stack<T> {
var items = [T]() mutating func push(item: T) {
items.append(item)
} mutating func pop() -> T? {
if items.count == 0 {
return nil
}
return items.removeLast()
} func discretion() -> String {
return "这是一个泛型的栈结构类型,可以适用于所有类型"
}
} var intStack = Stack(items: [11, 5, 38])
let lastInt = intStack.pop()
print(lastInt) // 38
intStack.push(150)
print(intStack.items) // [11, 5, 150] var stringStack = Stack(items: ["Some String", "Alex"])
let lastString = stringStack.pop()
print(lastString) // Alex
stringStack.push("Alisa")
print(stringStack.items) // ["Some String", "Alisa"]

泛型类型可以理解为:泛型T定义了一个"某种类型T"、以便在类型内部用,当某种类型T被赋予一个类型后(如Int或String)不可更改为其他类型。

写法是:关键字(struct, class, enum) 类名<泛型> : 父类,或遵循的协议

扩展一个泛型类型

当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

struct Stack<T> {
var items = [T]() mutating func push(item: T) {
items.append(item)
} mutating func pop() -> T? {
if items.count == 0 {
return nil
}
return items.removeLast()
} func discretion() -> String {
return "这是一个泛型的栈结构类型,可以适用于所有类型"
}
} extension Stack {
// 一个计算属性,返回栈顶的item
var topItem: T? {
return items.isEmpty ? nil : items.last
}
}

类型约束

有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。

例如,Swift 的Dictionary类型对作用于其键的类型做了些限制。在字典的描述中,字典的键类型必须是可哈希,也就是说,必须有一种方法可以使其被唯一的表示。Dictionary之所以需要其键是可哈希是为了以便于其检查其是否已经包含某个特定键的值。如无此需求,Dictionary既不会告诉是否插入或者替换了某个特定键的值,也不能查找到已经存储在字典里面的给定键值。

这个需求强制加上一个类型约束作用于Dictionary的键上,当然其键类型必须遵循Hashable协议(Swift 标准库中定义的一个特定协议)。所有的 Swift 基本类型(如StringInt, Double和 Bool)默认都是可哈希。

约束语法:

func someFunction<T: SomeSuperClass, U: SomeProtocol>(someT: T, someU: U) -> someType {
// 函数体
}

约束实例:

func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
for (index, value) in array.enumerate() {
if value == valueToFind {
// 因为这里用到了"=="运算符,所以在泛型T后面要增加约束:T必须遵循Equtable协议,这样才能使用"=="运算符。
return index
}
}
return nil
} print(findIndex([10,22,86], valueToFind: 86)) // 2

关联类型

当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为typealias关键字。

非泛型类型例子:

protocol Container {
// 这是判断swift版本的方法
#if swift(>=2.2)
// 如果在2.2版本以上 用associatedtype关键字
associatedtype ItemType
#else
// 如果在2.2之下,还是用typealisa关键字
typealisa ItemType
#endif mutating func append(item: ItemType)
var count: Int { get }
subscript (i: Int) -> ItemType? { get }
} struct IntStack: Container {
var items = [Int]()
mutating func push(item: Int) {
items.append(item)
} mutating func pop() -> Int? {
if items.count == 0 {
return nil
}
return items.removeLast()
} // 遵循Container协议
typealias ItemType = Int
mutating func append(item: ItemType) {
self.push(item)
} var count: Int {
return items.count
} subscript (i: Int) -> ItemType? {
return items.isEmpty ? nil : items[i]
}
}

Container协议为了适配多个类型而声明了一个typealias Item(别名),后面的函数和属性都用到了这个别名,这个别名的具体类型需要遵循这个协议的类型来指定,如:typealias ItemType = Int。

泛型类型的例子:

protocol Container {
// 这是判断swift版本的方法
#if swift(>=2.2)
// 如果在2.2版本以上 用associatedtype关键字
associatedtype ItemType
#else
// 如果在2.2之下,还是用typealisa关键字
typealisa ItemType
#endif mutating func append(item: ItemType)
var count: Int { get }
subscript (i: Int) -> ItemType? { get }
} struct Stack<T>: Container {
var items = [T]() mutating func push(item: T) {
items.append(item)
} mutating func pop() -> T? {
if items.count == 0 {
return nil
}
return items.removeLast()
} // 遵循Container协议
typealias ItemType = T
mutating func append(item: ItemType) {
self.push(item)
} var count: Int {
return items.count
} subscript (i: Int) -> ItemType? {
return items.isEmpty ? nil : items[i]
}
}

Where语句

对关联类型定义约束是非常有用的。你可以在参数列表中通过where语句定义参数的约束。一个where语句能够使一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价关系。

func allItemMatch<C1: Container, C2: Container where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>(someContainer: C1, anotherContainer: C2) -> Bool {
// 检查两个Container的元素个数是否相同
if someContainer.count != anotherContainer.count {
return false
} // 检查两个Container相应位置的元素彼此是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
} // 如果所有元素检查都相同则返回true
return true
}

上面函数的详细解释:

这个函数有两个参数,类型分别用了泛型:C1、C2,但是对C1,C2做了类型约束。

  • C1,C2必须遵循Container协议。
  • C1的ItemType类型与C2的ItemType类型相同。
  • C1必须遵循Equatable协议。

第二条和第三条需要使用where语句,where语句的语法是 <泛型名 where 第一个约束, 第二个约束>。