什么时候在 Swift 中使用类型擦除?

When to use type erasure in Swift?

在 Swift 中已经有很多关于如何进行类型擦除的问题,而且我看到类型擦除通常被描述为处理具有关联类型和泛型类型的协议的重要模式。

但是,在我看来,需要类型擦除通常是设计问题的征兆——您本质上是在“丢弃”类型信息(即将值放入容器或将其传递给函数),这通常最终需要通过冗长和脆弱的向下转换来恢复。也许我不明白的是像 AnyHashable—PATs/protocols 这样的“类型”的用例 with self 只能用作通用约束,因为它们不是具体化的类型,这让我想知道什么有令人信服的理由想要具体化它们。

简而言之,什么时候 在 Swift 中使用类型擦除的想法?我正在寻找有关何时使用此模式的一些通用指南,以及可能的一些实际用例示例,其中类型擦除比其替代方案更可取。

我试图找到类型擦除的简约示例。根据我的经验,它通常会变得更加复杂,我尽量避免它。但有时就是这样。

终于和以前一样复杂了,用老派的语言。除了老式语言会因崩溃而伤害你,而 swift 在构建时伤害你。

它是一种强类型语言,因此它不适合泛型。

假设您需要管理文档中的一些形状。 这些形状是 Identifiables,这意味着它们有一个 id,其类型由关联类型决定。在这种情况下为 Int。

下面的代码无法构建,因为它不能直接使用 Shape 协议,因为 id 的类型是由符合 Shape 协议的对象定义的关联类型

import Foundation

protocol Shape: AnyShape, Identifiable {
    var name: String { get }
}

struct Square: Shape {
    var id: Int = 0
    var name: String { "Square" }
}

func selectShape(_ shape: Shape) {
    print("\(shape.name) selected")
}

通过添加类型擦除形状,您可以将其传递给函数。 因此,这将构建:

import Foundation

protocol AnyShape {
    var name: String { get }
}

protocol Shape: AnyShape, Identifiable {
    
}

struct Square: Shape {
    var id: Int = 0
    var name: String { "Square" }
}

func selectShape(_ shape: AnyShape) {
    print("\(shape.name) selected")
}

简单用例。

假设现在我们的应用程序连接到两个形状制造商的服务器以获取他们的目录并与我们的同步。

我们知道形状就是形状,遍布世界各地,但数据库中的 ACME Shape Factory 索引是 Int,而 Shapers俱乐部 使用UUID..

此时我们需要 'recover' 类型,如您所说。 这正是查看 AnyHashable 源文档时所解释的内容。

Cast是避免不了的,对于app的安全性和模型的稳固性来说总算是一件好事。

第一部分是协议,随着情况的增多,可能会比较冗长复杂,但属于app的通信基础框架,应该不会经常变动。

import Foundation

// MARK: - Protocols

protocol AnyShape {
    var baseID: Any { get }
    var name: String { get }
}

// Common functions to all shapes

extension AnyShape {
    
    func sameObject(as shape: AnyShape) -> Bool {
        switch shape.baseID.self {
        case is Int:
            guard let l = baseID as? UUID , let r = shape.baseID as? UUID else { return false }
            return l == r
        case is UUID:
            guard let l = baseID as? UUID , let r = shape.baseID as? UUID else { return false }
            return l == r
        default:
            return false
        }
    }

    func sameShape(as shape: AnyShape) -> Bool {
        return name == shape.name
    }

    func selectShape(_ shape: AnyShape) {
        print("\(shape.name) selected")
    }
}

protocol Shape: AnyShape, Identifiable {
    
}

extension Shape {
    var baseID: Any { id }
}

第二部分是模型 - 随着我们与更多形状制造商合作,这有望得到改进。

可以对形状进行的敏感操作不在此代码中。所以创建和调整模型和 api 没问题。

// MARK: - Models

struct ACME_ShapeFactory_Model {
    struct Square: Shape {
        var id: Int = 0
        var name: String { "Square" }

        var ACME_Special_Feature: Bool
    }
}

struct ShapersClub_Model {
    struct Square: Shape {
        var id: UUID = UUID()
        var name: String { "Square" }

        var promoCode: String
    }
}

测试

let shape1: AnyShape = ACME_ShapeFactory_Model.Square()
let shape2: AnyShape = ShapersClub_Model.Square()
let shape3: AnyShape = ShapersClub_Model.Square()

Compare two different shapes references from different manufacturers

shape1.sameObject(as: shape2) : false
-> Logic, it can't be the same item if it comes from different manufacturers

Compare two different shapes references from same manufacturers

shape2.sameObject(as: shape3) : false
-> This is a new shape, sync in catalog

Compare two identical shapes references from same manufacturers 

shape2.sameObject(as: shape2) : true
-> We already have this one in the catalog

Compare two shapes from different manufacturers

shape1.sameShape(as: shape2) : true
-> Dear customer, we have two kind of squares from two manufacturers

就是这样。我希望这可能有任何帮助。 欢迎任何更正或评论。

最后一句话,我为我的 Shapes 制造商的名字感到非常自豪:)