消除你的恐惧,这里是打字消除

188bet金宝搏官网OkCupid正在为iOS招聘单击此处了解更多信息

介绍

在OkCupid188bet金宝搏官网,我们一直在努力实现最佳的Swift实践。其中一个概念是类型擦除。你可能在想“我这辈子从来没有用过字型擦除……”但你错了!你不知道,任何对象实际上是一个删除任何物体. 你可能已经看过任何散布在Swift标准库中的前缀(任意哈希,任意迭代器,任意集合等)。

这种强大的结构是一把双刃剑。一方面,由于所有的类都符合任何对象,它充当所有类型的通用包装器,如果您需要处理未知类型,这将非常有用。另一方面,它消除了对斯威夫特类型安全系统的依赖。他们不会无缘无故地称之为“安全”!了解您正在处理的对象的类型有很多重要的好处,而且当您调用类型上未实现的方法时,我们都非常熟悉ole的Objective-C崩溃来自“未识别的选择器发送到实例”。

但是有一个两全其美的解决方案吗?在OkCupid,我们认为答案是肯定的。我们可以利用类型擦除的功能,将其应用到特定的用188bet金宝搏官网例中,以获得灵活收集的好处,同时保持Swift健壮的类型安全系统的刚性。

为了便于说明,我们将一个示例项目放在一起,其中包含应用程序中的代码,这些代码为我们的对话视图控制器实现了一种特殊形式的类型擦除。

https://github.com/188bet金宝搏官网OkCupid/swift-type-erasure

Ivborw0kgoaaaansuheugaabamaaszcayaaaciwkb7aaabgmldq1bzukdcielfqzyxoty2ltiu-5公司

问题

在这个例子中,我们将构建一个模型来表示OkCupid iOS应用程序中的对话。188bet金宝搏官网

在为对话屏幕构建模型层时,我们有几个目标:

  1. 我们需要良好的抽象来支持将来特性的持久性
  2. 对于单元测试,模型需要易于模拟
  3. 架构应该是可扩展的,以允许我们根据需要扩展逻辑

创建简单结构是我们的第一种方法:

结构对话{let threadId:String let isUnread:Bool let通讯器:User//。。以此类推}

然而,我们很快意识到,这种方法并没有达到我们的所有目标。具体来说,这并没有给我们所需要的可扩展性。例如,如果OkCupid要引入一种新的会话类型,我们需要重构大量代码来支持它。188bet金宝搏官网

这些“未来”类型的一些例子可能是持续对话,小组对话,秘密谈话等等,因为对话是一个具体的结构,在整个应用程序中支持多种类型的对话需要一个重要的重构。

因为我们知道任何类型的会话都将共享相同的基本属性集,所以让我们使用协议抽象出具体的类型。

面向协议的方法

让我们创建会话协议,一种包含会话的所有共享属性的协议。

协议会话协议{var threadId:String{get}var通讯器:UserProtocol{get}var isUnread:Bool{get}//etc。。}

会话协议协议非常适合表达共享属性需求,但我们也希望在会话中共享一些功能。例如,我们需要一种方法来区分一个会话和另一个会话。幸运的是,斯威夫特为我们提供了可散列的协议(从相等的协议)。

我们想要的是可散列的可以在当前和未来的具体实现中共享会话协议. 然而,协议是不可能的(会话协议)实现另一个协议(可散列的).我们需要一个具体的类型

我们可以使用哪里扩展中的声明,特别适用于具体类型:

扩展会话协议,其中Self:Hashable{func hash(into-hasher:inout-hasher){哈希器.组合(threadId)}func isEqualTo(uother:ConversationProtocol)->Bool{guard let otherConversation=other as?Self else{return false}return threadId==其他会话.threadId}static func==(lhs:Self,rhs:Self)->Bool{返回左S.isEqualTo(右)}

这告诉编译器会话协议可散列的免费获取默认实现!这是非常强大的,因为它允许一个单一的可散列的在所有混凝土类型中会话协议.

要使用它,我们只需创建一个具体类型:

结构会话:ConversationProtocol{var threadId:String var responent:UserProtocol var isUnread:Bool}

现在,使用我们的共享实现可散列的我们需要做的就是:

//这就是魔法发生的地方✨ 扩展会话:可哈希{}

现在我们有了混凝土对话自动获取可散列的只需提供一个空白的实现。

但是。。。在我们尝试存储会话协议在集合中。。。毕竟,我们仍然希望使用抽象类型来实现最大的灵活性。

var conversationSet=Set()

编译器在对我们吼叫。。。

错误:类型“ConversationProtocol”不符合协议“Hashable”

这是有意义的,因为协议不能实现协议。那你怎么做一套会话协议什么?这就是类型擦除发挥作用的地方。

我们需要一个符合Hashable的具体类型来存储在一个集合中。

struct AnyHashableConversation:ConversationProtocol{var threadId:String{return会话.threadId}var对应:UserProtocol{return对话.通讯员}var isUnread:Bool{return对话.isUnread}私有let会话:ConversationProtocol init(会话:ConversationProtocol){自我对话= conversation}}//使用我们现有的Hashable实现扩展AnyHashableConversation:Hashable{}

就这样!现在我们可以创建一组任何会话类型,并使用可散列的!

let conversations=Set()

结论

结合使用协议和类型擦除是一种在许多具体类型中抽象公共功能的有用技术。它允许我们保持我们的代码松散耦合,可扩展,易于测试。您是否有使用类型擦除或其他抽象来解决类似问题的经验?把你的想法留给我们吧,我们很想了解更多!

标题图片由以下人员提供:https://www.iteratorshq.com/blog/scala-compiler-phases-with-pictures/type-erasure/