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

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

消除你的恐惧,在这里输入消除图片来源

介绍

在OkCupid188bet金宝搏官网,我们一直在努力实现最好的Swift实践。其中一个概念是类型擦除。你可能会对自己说:“我这辈子从来没有使用过类型擦除……”但是你错了!你不知道,实际上是一个协议,它擦除了具体类型的任何对象.你们可能见过任何前缀散落在Swift标准库(AnyHashableAnyIteratorAnyCollection等等)。

这种强大的构造是一把双刃剑。一方面,因为所有的类都符合AnyObject,它充当所有类型的通用包装器,这在您需要使用未知类型时可能很有用。另一方面,它消除了对Swift类型安全系统的依赖。他们不会无缘无故叫它"安全"的!知道你正在使用的对象的类型是很重要的,我们都非常熟悉当你在一个类型上调用一个未实现的方法时“Unrecognized selector sent to instance”中的Objective-C崩溃。

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

为了便于说明,我们将一个示例项目放在一起,其中包括来自应用程序的代码,为我们的conversation视图控制器实现了Type Erasure的特殊形式。

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

这个问题

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

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

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

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

struct谈话{
让threadId:字符串
让isUnread: Bool
让记者:用户
/ / . .等等

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

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

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

面向协议的方法

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

协议ConversationProtocol {
var threadId: String {get}
var通讯器:UserProtocol {get}
var isUnread: Bool {get} //等。

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

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

方法可以在另一个协议上提供协议的默认实现在哪里在扩展中声明,它特别适用于具体类型:

扩展converationprotocol,其中Self: Hashable {

func hash(into hash: inout hash) {
hasher.combine (threadId)


func isEqualTo(_ other: ConversationProtocol) -> Bool {
guard let otherConversation = other as?自其他{
返回假


返回threaddid == otherconversation . threaddid


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 AnyHashableConversation: ConversationProtocol {

var threadId: String {
返回conversation.threadId


var通讯器:UserProtocol {
返回conversation.correspondent


var isUnread: Bool {
返回conversation.isUnread


private let conversation: ConversationProtocol

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



//使用现有的Hashable实现
扩展AnyHashableConversation: 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金宝搏官网