** 这是我的集合系列文章的第一篇,计划从浅入深通过一系列文章将swift的集合相关内容彻底整理清楚,包括集合类相关的第三方代码库,最后自定义一个集合类型,把所有的内容用代码贯穿起来。**
我们之前在实现 SequenceType 协议的时候(Generators VS Sequences VS Collections),使用了 AnyGenerator 这个泛型结构体。在swift的文档中,使用了 “type erase” 这个词来说明 AnyGenerator,我们这次就是掰扯掰扯啥是“type erase”。
为什么需要类型擦除
在swift中,以Any开头的都有类型擦除(type erase)的味道,比方说 AnySequence、AnyForwardIndex、AnyRandomAccessCollection等。当然还有我们最熟悉的Any与AnyObject。
我们首先来看这样一个代码片段:
let seq = AnySequence([1,2,3])
我们用这样方式将数组[1,2,3]转化为一个AnySequence<Int>,可是这种做法会让人很奇怪,因为[1,2,3]自己就是sequence啊,原本就可以进行迭代操作的。那我们为啥还要转成AnySequence呢?这个原因在于[1,2,3]除了是sequence之外,它还是一个数组,有时候我们确实是希望隐藏掉它细节类型(数组)这件事情,而支对外暴露sequence特性。
如果你此时还不是很了解sequence,可以看另外一篇内容Generators VS Sequences VS Collections。
比方说,我们考虑一下这样的情况:
let xs = [1,2,3]
let ys = ["A","B","C"]
let zs = zip(xs.reverse(), ys.reverse())
这个时候 zs 的类型是Zip2Sequence<ReverseRandomAccessCollection<Array<Int>>, ReverseRandomAccessCollection<Array<String>>>,那我们会不会去实现一个方法来返回一个这样的类型呢?比方说就像下面这样:
func reverseZip<T,U>(xs: [T], _ ys: [U]) -> Zip2Sequence<ReverseRandomAccessCollection<[T]>, ReverseRandomAccessCollection<[U]>> {
return zip(xs.reverse(), ys.reverse())
}
我相信你一定不会赞同这样做的,因为这样会完整的暴露了实现的细节,如果有一天,我们把返回值调整成为这个样子:
return zip(xs, ys).reverse()
那么我们的返回值的类型就变成 [(T,U)] ,那么所有调用该方法的地方都要面临着修改。这就是我们过多暴露内部实现所带来的危害。其实此时我们只要返回一个tuples的sequence就可以了,但在很多时候,我们都使用了Array来替代了sequence,我们完全可以使用下面的返回类型:
func reverseZip<T,U>(xs: [T], _ ys: [U]) -> AnySequence<(T,U)> {
return AnySequence(zip(xs, ys).reverse())
}
通过这样做,我们实现了类型擦除,这样既可以防止实现细节过多的暴露,又可以满足调用者迭代的需求。
AnySequence与Any、AnyObject的类型擦除不同,Any、AnyObject只是把类型隐藏的,我们可以通过 as! AnyObject操作来重新获得原始类型,但AnySequence真的把封装在其内部的数据的类型给隐藏起来了,我们没法获得数据的原始类型了。
让我们开始擦除
让我们看下面这个例子,当然,考验我们基本功的时间也到了.
protocol Animal {
associatedtype Food
func feed(food: Food)
}
// Kinds of Food
struct Grass {}
struct Worm {}
struct Cow: Animal {
func feed(food: Grass) { print("moo") }
}
struct Goat: Animal {
func feed(food: Grass) { print("bah") }
}
struct Bird: Animal {
func feed(food: Worm) { print("chirp") }
}
我们首先定义一个协议Animal,并用Cow、Goat、Bird分别实现了Animal,然后我们定义了两种食物,
Grass和Worm,我们知道,牛(Cow)和羊(Goat)吃草(Grass),而鸟(Bird)吃虫(Worm)。
现在需求来了,我们需要给吃草的动物喂草,我们需要这样做:
for animal in grassEaters {
animal.feed(Grass())
}
所以我们需要定义一个:只吃草类型动物的数组,我们判断一下如果这样操作是否可行:
let grassEaters = [Cow(), Goat()]
结论是否定的,原因就留给你自己补充了。那我们进行一次调整,用显示声明的办法,指明数组的类型为Animal,你认为这样是否可行?
let grassEaters: [Animal] = [Cow(), Goat()]
Cow 和 Goat 都属于 Animal 类型,看上去好像可行啊,实际上我们会收到错误提示:protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements。协议毫无疑问是可以用来定义类型的,比方说作为参数类型或返回值类型。问题出在我们定义的Animal是泛型的,也就是说Animal是无法表示表示某一种特定类型的。
let grassEaters: [Animal<Grass>] = [Cow(), Goat()]
这种写法有什么问题呢?和上面的错误相同,另外泛型的占位符不可以是具体类型。
protocol Animal<Food> {
func feed(food: Food)
}
这种写法的错误在于:协议是没有泛型写法的,只能通过associatedtype关键字来配合实现泛型的效果。为什么这样?可以看另外一篇内容 swift协议为什么不用 <T>语法
那我们到底应该怎样做呢?我们要做的是要实现一个类似AnySequence的东西,我们起名字叫做AnyAnimal,用来擦除一个动物的具体类型,让我们来看一下具体的实现:
struct AnyAnimal<Food>: Animal {
private let _feed: (Food) -> Void
init<Base: Animal where Food == Base.Food>(_ base: Base) {
_feed = base.feed
}
func feed(food: Food) { _feed(food) }
}
AnyAnimal是一个泛型的结构体,因为它实现了Animal协议,所以当用它的构造函数接受一个具体动物来创建实例的时候,相当于擦除了动物的具体类型,因为我们得到的是一个 AnyAnimal<Food>类型的实例。相对于我们上面的所有错误示例而言,这个代码片段的优点在于
- 泛型构造器有效的控制了动物所吃的食物类型 与AnyAnimal指定的食物类型匹配
- feed方法用调用闭包的方式实现,
现在我们就可以创建grassEaters了。
let grassEaters = [AnyAnimal(Cow()), AnyAnimal(Goat())]
此时grassEaters的类型为 [AnyAnimal<Grass>],在这样的情况下,我们不但创建数组成功,而且还可以获得编译器帮我们提供的类型检查。比方说我们这样创建grassEaters的话:
let mixedEaters = [AnyAnimal(Cow()), AnyAnimal(Bird())]
编译器会报错,因为我们在数据中使用了混合类型,问题出在Bird.Food != Cow.Food.
我们使用这种类型擦除方式可以让我们使用了associatedtype关键字的协议转化为一个泛型类型,这就意味着我们可以把做作为属性或者返回值等我们无法直接使用协议的地方。
本文参考 [http://robnapier.net/erasure]{http://robnapier.net/erasure}
网友评论