如何使用类型擦除来实现更好的模型抽象

188bet金宝搏官网OkCupid正在招聘iOS用户点击这里了解更多

消除你的恐惧,打字消除就在这里图片来源

介绍

在OkCupid188bet金宝搏官网,我们一直在努力实现班上最好的Swift实践。其中一个概念是类型擦除。你可能会想“我这辈子从来没用过type erase”,但你错了!你不知道的是,它实际上是一种消除了具体类型的任何对象.你可能已经看到了任何散布在Swift标准库的前缀(无论如何AnyIteratorAnyCollection等等)。

这个强大的结构是一把双刃剑。一方面,既然所有的阶级都符合AnyObject,它充当所有类型的通用包装器,如果您需要处理未知类型,这可能很有用。另一方面,它消除了对Swift的类型安全系统的依赖。他们不会无缘无故叫它“安全”的!知道你正在处理的对象的类型有很重要的好处,我们都太熟悉ole' Objective-C崩溃从“Unrecognized selector sent to instance”当你调用一个方法未实现在一个类型上。

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

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

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

这个问题

对于此示例,我们将建立代表Okcupid iOS应用程序中对话的模型。188bet金宝搏官网

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

  1. 我们需要良好的抽象来支持未来特性的持久性
  2. 需要轻松咀嚼单元测试所需的模型
  3. 体系结构应该是可扩展的,以允许我们根据需要扩展逻辑

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

结构对话{
留言机:字符串
让isUnread: Bool
让记者:用户
/ / . .等等

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

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

由于我们知道任何类型的对话都会共享相同的基本属性,让我们抽象使用协议的具体类型。

面向协议的方法

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

协议ConversationProtocol {
var trainid:string {get}
var对应:UserProtocol {get}
var isUnread: Bool {get} //等等。

ConversationProtocol协议非常适合表达共享属性需求,但我们也希望在我们的对话中实现一些功能共享。例如,我们需要一种方法来区分不同的对话。幸运的是,斯威夫特为我们提供了Hashable协议(继承自Equatable协议)用于此目的。

我们想要的是一个单一的实现Hashable可以在当前和未来的具体实施中共享ConversationProtocol.然而,协议(ConversationProtocol)来实现另一个协议(Hashable).我们需要一个具体的类型

我们可以通过使用通过使用该协议的默认实现在哪里扩展中的声明,特别适用于具体类型:

扩展对话前容,其中自我:hashable {

Func Hash(进入Hasher:Inout Hasher){
hasher.combine (threadId)


func isequalto(_其他:connableationProtocol) - > BOOL {
守卫让别人谈话=其他的?自其他{
返回假


return trainid == otherconversation.threadid


static func ==(lhs: Self, rhs: Self) -> Bool {
返回lhs.isequalto(RHS)

这告诉编译器所实现的任何具体类型ConversationProtocolHashable免费获取默认实现!这是非常强大的,因为它允许一个单一的中央实现Hashable在所有具体类型中ConversationProtocol

要使用它,我们需要做的就是创建一个具体类型:

struct Conversation: ConversationProtocol {
var threadId:字符串
var记者:UserProtocol
var isunread:bool

现在,使用我们的共享实施Hashable我们需要做的就是

//这是魔法发生的地方♥
会话:Hashable {}

现在我们有一个具体的谈话自动获得实现的结构体Hashable只需提供一个空白的实现。

但是,这一切都很棒,直到我们试图存储我们的实例ConversationProtocol在设置…毕竟,我们仍然希望使用抽象类型来实现最终的灵活性。

var conversationSet = Set()

编译器对我们大喊大叫......

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

这是有意义的,因为协议不能实现协议。那么如何做一套ConversationProtocol年代?这就是类型删除发挥作用的地方。

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

struct anyhashablconversation: ConversationProtocol {

var threaddid: String {
返回conversation.threadId


var对应:UserProtocol {
返回对话.Corresponent.


var isUnread: Bool {
退回对话


私人允许对话:对话方案

init(对话:ConversationProtocol) {
自我。谈话=谈话



//使用我们现有的Hashable实现
扩展anyhashablconversation: Hashable {}

就是这样!现在我们可以创建任意会话类型的集合,它将使用单一的共享实现进行散列Hashable

let conversations = Set()

结论

协议与类型擦除相结合的使用是一种用于抽象许多具体类型中的常用功能的有用技术。它允许我们保留我们的代码松散耦合,可扩展,易于测试。您是否拥有使用类型擦除或其他抽象的经验来解决类似的问题?让我们留下你的想法,我们喜欢了解更多!

最初发表在https://tech.188bet金宝搏官网okcupid.com2019年11月4日。

188bet金宝搏官网OkCupid科技博客

阅读Okcupid工程团队每天连接数百万人的188bet金宝搏官网故事

188bet金宝搏官网OkCupid科技博客

188bet金宝搏官网OkCupid的工程团队负责每天为数百万人配对。在OkCupid科技博客上阅读他们的故事188bet金宝搏官网

188bet金宝搏官网OkCupid科技博客

188bet金宝搏官网OkCupid的工程团队负责每天为数百万人配对。在OkCupid科技博客上阅读他们的故事188bet金宝搏官网