泛型
泛型代码可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型
泛型所解决的问题
定义一个swapTwoInts方法,用于交换两个int类型的值
一:swapTwoInts
func swapTwoInts(inout a: Int,inout b: Int) { let temporaryA = a a = b b = temporaryA }
二:swapTwoStrings
func swapTwoStrings(inout a: String,inout b: String) { let temporaryA = a a = b b = temporaryA }
三:swapTwoDoubles
func swapTwoDoubles(inout a: Double,inout b: Double) { let temporaryA = a a = b b = temporaryA }
上述定义了三个方法swapTwoInts、swapTwoStrings、swapTwoDoubles功能都是相同的(交换两个数的值),唯一不同的是传入的变量类型不同
泛型函数(inout关键字表示参数是地址)
func swapTwoValues<T>(inout a: T,inout b: 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来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。
泛型类型
非泛型版本的栈
struct IntStack { var items = [Int]() mutating func push(item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } }
泛型版本的栈
struct Stack<T> { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } }
var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") stackOfStrings.push("cuatro") // 现在栈已经有4个string了 let fromTheTop = stackOfStrings.pop() // fromTheTop is equal to "cuatro",and the stack now contains 3 strings
泛型类型拓展
向泛型栈中添加了一个只读的计算属性topItem,这个属性返回一个可选类型T的值。若栈不为空,则返回栈顶元素
extension Stack { var topItem: T? { return items.isEmpty ? nil : items[items.count - 1] } }
类型约束
swapTwoValues函数和Stack类型可以作用于任何类型,不过,有的时候对使用在泛型函数和泛型类型上的类型强制约束为某种特定类型是非常有用的。类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
类型约束语法
func someFunction<T: SomeClass,U: SomeProtocol>(someT: T,someU: U) { //语法 }
上面这个假定函数有两个类型参数。第一个类型参数T,有一个需要T必须是SomeClass子类的类型约束;第二个类型参数U,有一个需要U必须遵循SomeProtocol协议的类型约束。
类型约束行为
名为findStringIndex的非泛型函数,该函数功能是去查找包含一给定String值的数组
func findStringIndex(array: [String],valueToFind: String) -> Int? { for (index,value) in enumerate(array) { if value == valueToFind { return index } } return nil } let strings = ["cat","dog","llama","parakeet","terrapin"] if let foundIndex = findStringIndex(strings,"llama") { println("The index of llama is \(foundIndex)") } // prints "The index of llama is 2"
Swift 标准库中定义了一个Equatable协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持Equatable协议。
func findIndex<T: Equatable>(array: [T],valueToFind: T) -> Int? { for (index,value) in enumerate(array) { if value == valueToFind { return index } } return nil } //T: Equatable,也就意味着“任何T类型都遵循Equatable协议”。 let doubleIndex = findIndex([3.14159,0.1,0.25],9.3) // doubleIndex is an optional Int with no value,because 9.3 is not in the array let stringIndex = findIndex(["Mike","Malcolm","Andrea"],"Andrea") // stringIndex is an optional Int containing a value of 2
关联类型
当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型给定作用于协议部分的类型一个节点名(或别名)。作用于关联类型上实际类型是不需要指定的,直到该协议接受。关联类型被指定为typealias关键字
关联类型行为
protocol Container { typealias ItemType mutating func append(item: ItemType) var count: Int { get } subscript(i: Int) -> ItemType { get } }
Container协议定义了三个任何容器必须支持的兼容要求:
一、必须可能通过append方法添加一个新item到容器里;
二、必须可能通过使用count属性获取容器里items的数量,并返回一个Int值;
三、必须可能通过容器的Int索引值下标可以检索到每一个item。
struct EmpIntStack: 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 ItemType = Int mutating func append(item: Int) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Int { return items[i] } }
<span class="s1">//IntStack</span><span class="s2">指定了</span><span class="s1">Container</span><span class="s2">的实现,适用的</span><span class="s1">ItemType</span><span class="s2">被用作</span><span class="s1">Int</span><span class="s2">类型。对于这个</span><span class="s1">Container</span><span class="s2">协议实现而言,定义</span><span class="s1">typealias ItemType = Int</span><span class="s2">,将抽象的</span><span class="s1">ItemType</span><span class="s2">类型转换为具体的</span><span class="s1">Int</span><span class="s2">类型。</span>
你也可以生成遵循 Container 协议的泛型 Stack 类型:
struct EmpStack<T>: Container { // original Stack<T> implementation var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } // conformance to the Container protocol mutating func append(item: T) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> T { return items[i] } } //这个时候,占位类型参数T被用作append方法的item参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的ItemType的T的合适类型
Where 语句
一个where语句使你能够要求一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可写一个where语句,通过紧随放置where关键字在类型参数队列后面,其后跟着一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型的等于关系。
下面的列子定义了一个名为allItemsMatch的泛型函数,用来检查是否两个Container单例包含具有相同顺序的相同元素。如果匹配到所有的元素,那么返回一个为true的Boolean值,反之,则相反。
func allItemsMatch<C1: Container,C2: Container where C1.ItemType == C2.ItemType,C1.ItemType:Equatable> (someContainer: C1,anotherContainer: C2) -> Bool { // 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 are equivalent for i in 0...someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // all items match,so return true return true }
这个函数用了两个参数:someContainer和anotherContainer。someContainer参数是类型C1,anotherContainer参数是类型C2。C1和C2是容器的两个占位类型参数,决定了这个函数何时被调用。
这个函数的类型参数列紧随在两个类型参数需求的后面:
①C1必须遵循Container协议 (写作 C1: Container)。
②C2必须遵循Container协议 (写作 C2: Container)。
③C1的ItemType同样是C2的ItemType(写作 C1.ItemType == C2.ItemType)。
④C1的ItemType必须遵循Equatable协议 (写作 C1.ItemType: Equatable)。
③和④要求被定义为一个where语句的一部分,写在关键字where后面,作为函数类型参数链的一部分。
var empStackOfStrings = Stack<String>() empStackOfStrings.push("uno") empStackOfStrings.push("dos") empStackOfStrings.push("tres") var arrayOfStrings = ["uno","dos","tres"] //if allItemsMatch(empStackOfStrings,arrayOfStrings) { // println("All items match.") //} else { // println("Not all items match.") //} // prints "All items match."