Swift & Cocoa 实战之泛型编程:Swift 中的泛型编程
Swift 中的泛型编程本文中,笔者旨在对于Swift中的泛型编程进行一个综合性描述。读者可以查看上一篇 系列中的描述来看之前笔者的论文。泛型编程是编程方式的一种,主要是一些譬如类、结构体以及枚举这样的复杂类型以及函数可以使用类型参数进行定义(type parameters)。类型参数可以看做是真实类型的占位符,当泛型或者函数被使用时才会被真实类型替换。 在Swift中,对于泛型编程最直观的表现当属Array这个类型。在Objective-C中,Array的示例NSArray可以包含任意类型的对象。但是,Swift中的Array类型需要在声明时即指定它们包含的元素类型,,譬如Array<Int>,Array<UIView>。Array与Int都是一种数据类型,泛型机制允许这两种类型有机组合起来从而能够传递更多的额外的信息。 备注:在Swift中惯用的声明Array包含了Foo类型的用法是 Why generics (为什么使用泛型)?这里列举出几个静态类型语言中使用泛型编程的原因:
Generic entities (泛型实体)Swift中的泛型编程主要表现在以下两个不同的情况下:当定义某个类型或者定义某个函数时。泛型类型的特性以及 Generic types (泛型类型)Swift中主要的三个用户可自定义的类型可以被当做泛型,下面就以Result枚举类型为例,该类型中存放了表征成功的Success以及表征失败的Failure: enum Result<T,U> { case Success(T) case Failure(U) } 在Result类型之后有两个类型参数: T以及U。这些类型参数将会在创建实例时被替换为真实的类型: let aSuccess : Result<Int,String> = .Success(123) let aFailure : Result<Int,String> = .Failure("temperature too high") 泛型类型的类型参数可以被用于以下的几个方面:
泛型可以在以下两种方式中被初始化:
struct Queue<T> { /* ... */ } // First way to instantiate let a1 : Queue<Int> = Queue() let a2 : Queue<Int> = Queue.staticFactoryMethod() // or just .staticFactoryMethod() // Second way to instantiate let b1 = Queue<Int>() let b2 = Queue<Int>.staticFactoryMethod() 注意,像T这样的类型参数在类型定义时出现,无论这个类型何时被调用,这些类型参数都会被替换为真实的类型。举例而言, Generic functions (泛型函数)函数、方法、属性、下标以及初始化器都可以当做泛型进行处理,它们自身可以成为泛型或者存在于某个泛型类型的上下文中: // Given an item,return an array with that item repeated n times func duplicate<T>(item: T,numberOfTimes n: Int) -> [T] { var buffer : [T] = [] for _ in 0 ..< n { buffer.append(item) } return buffer } 同样的,类型参数是定义在
当然,如果所有的类型参数都是未用状态编译器会不太友好。而泛型方法可以同时定义在泛型类型与非泛型类型上: extension Result { // Transform the value of the 'Result' using one of two mapping functions func transform<V>(left: T -> V,right: U -> V) -> V { switch self { case .Success(let value): return left(value) case .Failure(let value): return right(value) } } } 上述中的 // Example use of the 'duplicate:numberOfTimes:' function defined earlier. // T is inferred to be Int. let a = duplicate(52,numberOfTimes: 10) 实际上,尝试去清楚地设置参数类型还会引发错误: // Does not compile // Error: "Cannot explicitly specialize a generic function" let b = duplicate<String>("foo",numberOfTimes: 5) Associated types (关联类型)Swift中的Protocol不可以使用类型参数定义泛型,不过Protocol可以使用 // A protocol for things that can accept food. protocol FoodEatingType { typealias Food var isSatiated : Bool { get } func feed(food: Food) } 在这个实例中,Food是定义在FoodEatingType协议中的关联类型,而某个协议中的关联类型数目可以根据实际的需求数量定义。 关联类型,类似于类型参数,都是一种占位符。而后面如果某个类需要实现这个协议,则需要确定 class Koala : FoodEatingType { var foodLevel = 0 var isSatiated : Bool { return foodLevel < 10 } // Koalas are notoriously picky eaters func feed(food: Eucalyptus) { // ... if !isSatiated { foodLevel += 1 } } } 对于 // Gourmand Wolf is a picky eater and will only eat his or her favorite food. // Individual wolves may prefer different foods,though. class GourmandWolf<FoodType> : FoodEatingType { var isSatiated : Bool { return false } func feed(food: FoodType) { // ... } } let meela = GourmandWolf<Rabbit>() let rabbit = Rabbit() meela.feed(rabbit) 在上述代码中, // Types that conform represent simple max-heaps which use their elements as keys protocol MaxHeapType { // Elements must support comparison ops; e.g. 'a is greater than b'. typealias Element : Comparable func insert(x: Element) func findMax() -> Element? func deleteMax() -> Bool } Type constraints (类型约束)截至目前,我们提及的泛型,诸如T以及U可以用任意类型来替换。而标准库中的Array类型即是这样一种无任何约束的典型,可以由其类型参数来决断。譬如下面一个例子中,需要编写一个函数,输入一个数组而获取数组中最大的那个值并且返回: // Doesn't compile. func findLargestInArray<T>(array: Array<T>) -> T? { if array.isEmpty { return nil } var soFar : T = array[0] for i in 1..<array.count { soFar = array[i] > soFar ? array[i] : soFar } return soFar } 不过这样毫无限制的泛型对于编译器是非常不友好的,如上述代码中需要进行一个比较,即 在上文对于Protocol的讨论中,我们已经尝试使用 // Note that <T> became <T : Comparable>,meaning that whatever type fills in // 'T' must conform to Comparable. func findLargestInArray<T : Comparable>(array: Array<T>) -> T? { if array.isEmpty { return nil } var soFar : T = array[0] for i in 1..<array.count { soFar = array[i] > soFar ? array[i] : soFar } return soFar } // Example usage: let arrayToTest = [1,2,3,100,12] // We're calling 'findLargestInArray()' with T = Int. if let result = findLargestInArray(arrayToTest) { print("the largest element in the array is (result)") } else { print("the array was empty...") } // prints: "the largest element in the array is 100" 这是因为,在某种意义上,存在着一种悖论,越限制类型参数与添加约束,用户越能方便地使用这些参数。不加任何限制地类型参数往往只能使用在简单地交换或者从集合中添加或者删除某些元素。 简单约束func example<T,U : Equatable,V : Hashable>(foo: T,bar: U,baz: V) { // ... } 如果需要对泛型进行细粒度的控制,首先来讨论下在哪些地方可以进行控制处理:
Swift中提供了以下三个类型的约束:
Putting it all together上文中已经介绍了基本的泛型的用法和约束,而对于泛型类型的签名可以综合使用如下:
protocol Foo { typealias Key typealias Element } protocol Bar { typealias RawGeneratorType } func example<T : Foo,U,V where V : Foo,V : Bar,T.Key == V.RawGeneratorType,U == V.Element> (arg1: T,arg2: U,arg3: V) -> U { // ... } 不要惊慌,我们会一步一步地介绍这些泛型的用法。在
综上所述,无论何时使用 Constrained extensionsSwift 2中新近提出了约束扩展的概念,一个更强大的能够使用泛型的语法特性。在Swift中,扩展允许向任意类型,即使尚未定义的类型中添加方法。同样允许向某个协议中添加默认的实现方法。同样的,这样的基于约束的扩展可以方便某些泛型的用法:
Syntax and limitations (语法与限制)基于约束的扩展语法如下所示: // Methods in this extension are only available to Arrays whose elements are // both hashable and comparable. extension Array where Element : Hashable,Element : Comparable { // ... } where关键字跟随在类型参数之后,而后跟着以逗号分割的一系列泛型类型和参数。不过这其中的约束中并不能限定为非泛型,即: // An extension on Array<Int>. // Error: "Same-type requirement makes generic parameter 'Element' non-generic" extension Array where Element == Int { // ... } 同时,也不能进行协议的传递: protocol MyProtocol { /* ... */ } // Only Arrays with comparable elements conform to MyProtocol. // Error: "Extension of type 'Array' with constraints cannot have an inheritance clause" extension Array : MyProtocol where Element : Comparable { // ... } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |