从 Swift 中的泛型谈起 | Soledad
众所周知,泛型是 Swift 中强大的功能之一,在标准库中泛型无处不在。讲解泛型的文章有很多,但是在这篇文章中,笔者将从苹果官方文档开始,以简明扼要的语言,一步步了解泛型。
Talk is cheap,show me the code.
例如,我们要实现一个函数,将一个给定整形数组中的每个元素都加 1。如:
1 2 3 4 5 6 7 | func incrementArray(xs:[Int])->[Int]{ var result:[Int] = [] for x in xs{ result.append(x + 1) } return result } |
我们现在还要实现另一个函数,将一个给定整形数组中的每个元素乘以 2:
1 2 3 4 5 6 7 | func doubleArray(xs:[Int])->[Int]{ var result:[Int] = [] for x in xs{ result.append(x * 2) } return result } |
这两个函数只有一行代码不同,因此我们可以将不同的地方抽象出来。在我们这里,一个是 x + 1,另一个是 x * 2,抽象出来的其实就是一个 (Int) -> Int 型的函数:
1 2 3 4 5 6 7 | func computeArray(xs:[Int],transform:(Int) -> Int)->[Int]{ var result:[Int] = [] for x in xs{ result.append(transform(x)) } return result } |
其实我们可以根据 computeArray 去生成 incrementArray 和 doubleArray。
1 2 3 4 5 6 7 | func incrementArray(xs:[Int]) -> [Int]{ return computeArray(xs){ x in x + 1 } } func doubleArray(xs:[Int]) -> [Int]{ return computeArray(xs){ x in x * 2 } } |
等一下,我们这里都是对整形进行的操作,如果我们想对整形数组进行判断,是偶数的返回 true,奇数的返回 false 又该怎么处理呢?如果按下面的写法:
1 2 3 4 | func isEvenArray(xs: [Int]) -> [Bool] { computeIntArray(xs) { x in x % 2 == 0 } } //会导致类型错误 |
有读者就要说了,那我们再写一个 computeBoolArray 的函数,将返回值改成 Bool 型不就好了吗?
这样做是可以。
那我们以后要返回整形对应的 String 类型又该怎么办呢?
泛型
这个时候我们就要引出泛型(generic)的概念了。
Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.
The Swift Programming Language
我们完全可以将computeArray写成如下的形式:
1 2 3 4 5 6 7 | func genericComputeArray<Element,T>(xs:[Element],transform: (Element) -> T) -> [T] { var result:[T] = [] for x in xs{ result.append(transform(x)) } return result } |
可能会有同学对 T 很陌生。我们来比较一下两个函数的第一行:
func computeArray(xs:[Int],transform:(Int) -> Int)->[Int]
func genericComputeArray(xs:[Element],transform: (Element) -> T) -> [T] ,t>
在泛型版本函数中,使用了 Element,T 这样的类型名,而没有使用具体的 Int,String 等。Element,T 实际的类型只有当每次 genericComputeArray 被调用时才会知道。
另外一个区别就是在函数名的后面使用了,其目的就是告诉 Swift 我这里要用 Element 和 T 来表示泛型了,你注意下。再次强调一下,T 只是一个 placeholder,Swift 不会去找一个叫做 T 的类型,T 类型也不是一个 NSObject 的子类。,t>
比起定义一个顶层 genericComputeArray 函数,按照 Swift 的惯性,我们将其定义为 Array 的扩展会更合适:
1 2 3 4 5 6 7 8 9 10 11 | extension Array{ //我们不妨将 genericComputeArray 更名为 CYMap func CYMap<T>(transform:(Element) -> T) -> [T]{ var result:[T] = [] for x in self{ result.append(transform(x)) } return result } } |
本文快结束了,有个惊喜:该 CYMap 函数已经是 Swift 标准库的一部分了(即 Array 中的 Map )。
One More Thing
问:如果我们有一个 startArray = [1,2,3],我们想将它每个数组元素先加1,再乘以2,最后转成字符串。
如果是你,你会怎么做?
1 | let array = stringArray(doubleArray(incrementArray(startArray))) |
这样吗?
其实,也可以只利用CYMap一个函数(用Swift自带的Map同样可以)就可以搞定。如下:
1 | let array = startArray.map { $0 + 1 }.map{ $0 * 2 }.map{ String($0) } |
(对上面代码中的 {} 化简和 $0 的含义不是很明白的同学请阅读官方文档中的 Closure 部分)
如果还有不懂的,自己再琢磨一下。另外,下面有参考链接或者可以直接联系我。
联系我
- 微博:@CaiYue_
- GitHub: caiyue1993
- 邮箱:yuecai.nju@gmail.com