如果你已经动手写过Swift的程序,相信你已经了解了Swift语言的知识,比如如何写类(class)和结构体(struct)。但Swift可没这么简单,呵呵呵。这篇教程主要讲述Swift的一个强力的特性:泛型。这个特性在很多程序设计语言里都非常受欢迎。
对于类型安全(type-safe)语言,一个常见的问题就是如何编写适用于多种类型输入的程序。想象一下,两个整型数相加和两个浮点数相加的程序看起来应该非常类似,甚至一模一样才对。唯一的区别就是变量的类型不同。
在强类型语言中,你需要去定义诸如addInts, addFloats, addDoubles 等方法来正确地处理参数及返回值。
许多编程语言已经解决了这个问题。例如,在C++中,使用Template来解决。而Swift,Java和C#则采用了泛型来解决这个问题。泛型,也是这篇文章要重点介绍的。
在这篇文章中,你将会学到Swift中如何使用泛型,也许你已经接触过,也许没有,不过没关系,我们会来一一探索。然后,我们会创建一个Flicker图片搜索应用,这个应用使用了自定义的泛型数据结构来保存用户搜索的内容。
备注:本文假设你已经对Swift有基本的了解或者有过Swift开发经验。如果你第一次接触Swift或者对Swift不是太了解,建议你首先阅读下other Swift tutorials。
泛型介绍
也许你不知道这个术语,但相信你已经在Swift中见到它了。Swift中的数组和字典类型就是使用泛型的经典例子。
Object-C开发者已经习惯使用数组和字典去保存多种数据类型。这种方式提供了很大的灵活性,但是谁又能知道一个API返回的数组里面到底是啥(数据类型)呢?你唯一能做的就是查看文档或者查看(方法的)变量命令(这也是另外一种文档哟!)。即使你查看了文档,你也不能保证程序在运行期不产生bug或者其他异常。
相比Object-C,Swift中的数组和字典都是类型安全的。一个Int型数组只可以保存Int而不可以保存String。这意味着你不用再查看文档啦,编译器就可以帮你做类型检查,然后你就就快可以愉快地coding了!
例如,在Object-C的UIKit中, 在自定义的View里面处理触摸事件可以这么写:
上述方法里面的set只可以保存UITouch实例, 因为文档里面就是这么说的。由于这个集合里面可以放任何对象,所以你需要在代码里面进行类型转换,也就是说把touches里面的对象转为UITouch对象。
当前Swift的标准库里面没有定义集合对象,但是你可以使用数组来代替集合对象,你可以用swift重写上面的代码:
上面的代码明确告诉你 touches数组只可以包含 UITouch实例, 否则编译器就会报异常。这样一来,你就再不用去做那烦人的类型转换了,因为编译器给你做了类型安全检查,保证数组里面只允许有 UITouch对象。
简要说来,泛型为类提供了一个类型参数。所有的数组都有相同的作用,即按顺序储存变量的数值,泛型数组除了多了一个类型参数之外,没有其他的不同之处。或许这样想更容易理解:你将要应用在数组上的各种算法和储存的数值类型无关,因此这些算法对于泛型数组和非泛型数组都适用。
既然你已经明白了泛型的基础知识和用法,那我们就开始把它应用在一个具体的例子上吧。
泛型实例
为了测试泛型,你将会编写一个在Flickr上搜索图片的应用。
首先请下载这个程序雏形,并尽快熟悉里面主要的类。其中Flickr类用于和Flickr的API交互。请注意这个类里面包含了一个API key(通常用于用户授权—译者注),但如果你想要扩展这个应用的话可能需要用自己的key,注册请点我。
构造并运行这个应用,你会看到这个:
好像什么都没有?别急,用不了多久你就可以让它帮你抓取可爱的喵图了!
有序字典(原文Ordered Dictionaries)
你的应用会根据每个用户的查询情况下载图片,按照图片被搜索到的频率由高到低排序并显示。
但如果用户对同样的关键字搜索了两次会怎样?如果这个应用能显示上次搜索的结果就好了。
或许用数组来实现这个功能也行得通,但为了学习泛型,你将会使用一个全新的数据结构:有序字典。
和数组不同的是,包括Swift在内地很多编程语言和框架都不保证集合(sets)和字典(dictionaries)的数据存储顺序。有序字典和普通的字典类似,不同之处在于它的key是有序的。你将会用这个特性,根据搜索关键字按顺序存储搜索结果。这样存储的好处是可以快速查询并更新图片列表。
一个草率的想法是自定义一个数据结构处理有序字典。但是你需要更加有前瞻性才行!你必须考虑到如何让你的应用在未来几年内都能正常工作!因此在这里使用泛型再合适不过了。
初始数据结构
点击“文件\新建\文件...”新建一个文件,并选择“IOS\Source\Swift File”。点击“下一步”并把这个文件命名为“OrderedDictionary”。最后,点击“创建”。
你会得到一个空的Swift文件,加这样一段代码进去:
到现在为止应该都没有什么问题。通过语义可以看出这个对象是一个结构体。
注意:总之,值的语义可以想象为“复制、粘贴的行为”,而不是“分享、参考的行为”。值的语义带来一系列的好处,例如不用担心一段代码无意地修改你的数据。了解更多,点击"Swift by Tutorials"的第三章节:类和结构体。
现在你需要将其一般化,以便它能够装载你需要的任何类型的数据。通过下列改变你对Swift中“结构”的定义:
在尖括弧中的元素是通用类型的参数。KeyType和ValueType不是他们自身的类型,而是你可以使用在结构里定义取代的类型。现在就简洁清新许多了!
最简单的实现一个有顺序的字典是保持一个数组和一个字典。字典中将会装载衍射,而数组将装载keys的顺序。
在结构体内部的定义中,加入以下的代码:
这样声明有两个目的,就像上例描述的,有两种类型的用于给已经存在的类型的取新的名称的别名。在这,你将分别地为后面的数组和字典赋值了别名。声明别名是将复杂类型定义为更短名称的类型的一种非常有效的方式。
你将注意怎么样从结构体中定义用“KeyType”和“ValueType”的参数类型中替换类型。上例的"KeyTypes"是数组类型的。当然这是没有这样的类型的“KeyType”;当在一般的实例化时,将替代Swift像对OrderedDictionary的类型的一切类型通过。
就因为这样,你将会注意到编译错误:
或许你会诧异怎么会这样?请再观察下Dictionary的继承者:
除了在KeyType之后的HashTable, 其他的都和OrderedDictionary的定义特别的相似。在分号后面为KeyType声明的Hashable,一定符合Hashable的协议。这是因为