类型擦除

下标不知道要打什么

前情提要

会想要打这篇是因为因为最近在看ReSwift跟Redux时发现ReSwift的源代码中有很多AnySubscriber,AnySubscription,SubscriptionBox之类奇怪的型态,而仔细看的话可以发现这些型态下面也有Subscriber,Subscription之类没有任何的型态。而造成的原因是什么呢?在解释前我们先来看一下例子吧!

会想要打这篇的另外一个原因是因为之前有看到跟Type Erasure有关的文章,但当时的我不太了解为什么要这样做,而最近看ReSwift时实在看太多type Erase了,于是决定把它搞懂。

先看个例子

只能这样了吗?

不,我才不要这样就放弃呢。

那我们只好把Pokemon这个形态给藏起来,不让swift编译器发现我们在干不法勾当。

如果我们不能使用Pokemon protocol当类型态,那我们能不能使用class将它包起来呢?可以!但应该怎么做呢?其实你在写swift时应有发现些东西叫做AnySequence,AnyIterator等等的东西,其实你仔细想想,Sequence是个协议,他不能当作型态使用,但有时我们在宣告阵列时他会是AnySequence型态。翻找一下swift源代码可以发现apple在实作这些wrapper的思路与实作方式,我们来看一下apple怎么写:

  ///一个类型擦除的序列。 
///
///`AnySequence`的实例将其操作转发到基础数据库
///具有相同“元素”类型的序列,隐藏了
///基础序列。
// @ _ versioned
@_fixed_layout
公共结构AnySequence :序列{
///创建一个新的序列,该序列将操作包装并转发到`base`。
@_inlineable
public init (_ base:S)
哪里
S.Element ==元素{
self._box = _SequenceBox(_base:基本)
...
}

可以看到AnySequence是把Sequence的操作进一步的拉出来的wrapper而已。我们需要做的事是做个init,并且检查Sequence中的Element跟初始化时的S.Element是否为同一型态,然后把操作拉出来即可(apple使用另一层wrapper:box把比较杂乱的代码藏起来了,打开AnySequenceBox可以看到进一步的实作)。

那我们现在也来做一样的事吧:

抓到法律的突破后,我们就有了看似真的AnyPokemon型态了,于是我们很开心的把神奇宝贝农场改成:

  let pokemonRanch:[AnyPokemon] = [] 

但别忘记AnyPokemon也算是某种程度的泛型型态,我们必须告诉他Power是什么:

  let pokemonRanch:[AnyPokemon ] = [] 

OK,这样写就没有error了。

但我们应该怎么把神奇宝贝放进去呢呢?

  let pokemon牧场:[AnyPokemon ] = [AnyPokemon(小火龙())] 

也许你会问,我不能直接放小火龙()吗?不行,因为小火龙()型态是,摁,小火龙。所以我们必须把它放进AnyPokemon走非法途径来骗过swift编译器。

那你一定会问那我可不可以把皮卡丘跟小火龙养在一起,现实世界也许可以,但在这边不行,你要再开另一个农场摆皮卡丘,于是乎我们农场可以这样开:

  let fireRanch:[AnyPokemon ] = [AnyPokemon(小火龙())] 
let electricRanch:[AnyPokemon ] = [AnyPokemon(皮卡丘())]

好啦,这样也是有很多好处的呀

  • 小火龙的火不会烧到皮卡丘
  • 皮卡丘不会电到小火龙
  • 不会交O产生皮卡龙或者小火丘等奇怪生物
  • 有一天抓到炎帝或雷公还可以放进你的农场中呢,毕竟属性(Power)一样嘛,这样可以避免混到不一样属性的神奇宝贝