1. Swift泛型的定义方法:
1) 和C++泛型概念一样,用法和C++也相似,同样也是使用一个类型占位符(代表一个通用类型,也是泛型的参数,用户随意取名,但要求按照标示符命名规则进行,因为其代表一个通用类型,因此和定义其它类型名的规范一样,最好是首字母大写的驼峰命名方式,一般取T);
2) 一个简单的泛型函数的例子:
func mySwap<T>(inout a: T, inout b: T) { let t = a a = b b = t } var a = 1, b = 2 mySwap(&a, &b) println(a) // 2 println(b) // 1 // Swift自己的库也含有swap泛型函数 swap(&a, &b) // 又换回来了 println(a) // 1 println(b) // 2 func myEqual<T: Comparable>(a: T, b: T) -> Bool { return a == b }此例子中类型占位符为T,可以当做普通类型一样使用;
3) 和任何一种语言的泛型一样,都是动态推导类型的,只有当实际传参的时候才会根据参数类型生成相应版本的代码(即运行时动态加载函数代码),因此执行效率较低,但是程序灵活性非常强;
4) 多类型参数:如果有多个类型占位符,则声明的时候用逗号,隔开
func f<T, K>(a: T, b: K) -> K { return b } println(f(12, "haha")) // haha
2. 泛型模板类以及扩展模板类:
1) 和C++模板类的概念一致,在类定义中可以含有类型占位符,用于表示一种通用类型;
!!泛型不仅支持函数,同时也支持结构体和枚举类型!
2) 定义类模板是需要在类名之后写上占位符,请看如下例子,模拟实现一个栈模板类:
struct Stack<T> { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } }
3) 扩展模板类时不需要重新写泛型参数列表,可以在扩展体中直接使用定义类时的泛型,非常方便:接上例代码
extension Stack { subscript(index: Int) -> T? { if index < 0 || index + 1 > countElements(items) { return nil } return items[index] } }
3. 泛型约束:
1) 顾名思义,就是要求那个类型占位符所代表的泛型遵守某些规矩,比如必须要遵守某些协议,或者必须由什么类继承而来等;
2) 实际上只能约束泛型遵守什么协议或者泛型继承自那个类,比如对于比较两个泛型是否内容相等,就不能直接使用普通的泛型,必须使用能遵守Equatable协议的两个泛型才能比较,该协议规定类型必须实现==和!=两种操作符的实现,可以想象,如果不遵守该协议,直接在泛型函数体中使用==操作符比较两个泛型是否相等,如果该泛型传进来的是一个用户自定义的类并且该类没有实现==操作符,那该如何让该泛型函数进行比较呢?请看以下的例子:
func equals<T: Equatable>(a: T, b: T) -> Bool { return a == b }如果要求遵守多个协议则用逗号隔开,就和继承类、遵守协议的语法一模一样,比如:
func equals<T: Equatable, Hashable>(a: T, b: T) -> Bool { // 既遵守Equatable协议也遵守Hashable协议 return a == b }3) 再来看一个例子,该例子是在一个泛型数组中查找某个元素并返回下标:
func findItemIndexFrom<T: Equatable>(arr: [T], byValue val: T) -> Int? { for (index, value) in enumerate(arr) { if value == val { return index } } return nil }
4. 关联类型——实现“泛型协议”:
1) Swift不运行定义泛型协议(至少语法上不允许像定义泛型类型那样定义泛型协议),但是有时有这方面的需要,比如在一个协议中指定一种泛型,然后协议中规定了很多方法或属性都需要用到该泛型,但一个类遵守该协议时再根据具体需要规定该泛型的具体类型,同时并使用该具体类型来实现协议中规定的方法和属性;
2) 答案时肯定的,Swift提供关联类型这种形式来解决以上问题,即可以先用typealias定义一个泛型(即不对该泛型赋值,值定义其名字),然后在规定要实现的属性或方法中使用该泛型,最后是当一个类型遵守该协议时再用typealias对该泛型进行赋值,使其成为某个具体的类型,接着再实现规定的属性和方法时用具体的类型替换该泛型即可:
protocol A { typealias ItemType // 先指定一个泛型 // 规定要实现的内容中暂时用泛型替代 mutating func push(item: ItemType) mutating func pop() -> ItemType } struct Stack<T>: A { var items = [T]() // 遵守协议时再将泛型和某种具体的类型“相关联”,而该某种具体的类型可以是模板类的类型占位符! // 可见Swift超级强大 typealias ItemType = T // 同样是可写可不写,编译器能自动推断 mutating func push(item: T) { // 实现时用具体类型代替关联类型 items.append(item) } mutating func pop() -> T { return items.removeLast() } } struct IntStack: A { // 一个Int的版本 var items = [Int]() typealias ItemType = Int // 词句可写可不写,编译器都能自动推断 mutating func push(item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } }
5. 用where语句对关联类型进行约束:
1) 和SQL的where约束类似,该约束发生在指定某个类型遵守某个协议(协议带有关联类型)时使用;
2) 具体语法如下:
protocol ProtocolStack { typealias ItemType mutating func push(item: ItemType) mutating func pop() -> ItemType var count: Int { get } subscript(index: Int) -> ItemType { get } } struct Stack<T>: ProtocolStack { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } var count: Int { return countElements(items) } subscript(index: Int) -> T { return items[index] } } func isAllItemMatched< STK1: ProtocolStack, STK2: ProtocolStack where STK1.ItemType == STK2.ItemType, STK1.ItemType: Equatable >(stk1: STK1, stk2: STK2) -> Bool { // 该函数用于比较两个栈中的内容是否完全相等 // 因此两个类型都必须遵守ProtocolStack协议 // 并且栈中元素必须遵守可比较协议Equatable // 元素进行比较的前提是两种栈中元素的类型必须相同,这也就是要求两种关联类型必须相同 if ( stk1.count != stk2.count ) { return false } for i in 0..<stk1.count { if stk1[i] != stk2[i] { return false } } return true }3) 对以上成果进行使用:泛型类型定义方法和C++类似,如下
var stk1 = Stack<Int>() var stk2 = Stack<Int>() for i in 0..<10 { stk1.push(i) stk2.push(i) } println(isAllItemMatched(stk1, stk2)) // true