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

问题描述

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

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

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

解决方法

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

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

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

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

下面的代码无法编译,因为它不能直接使用 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 Club 使用 { {1}}..

此时我们需要“恢复”类型,如您所说。 这正是查看 UUID 源文档时所解释的内容。

演员是无法避免的,对于应用的安全性和模型的稳固性来说,这最终是一件好事。

第一部分是协议,它可能会随着情况的增加而变得冗长和复杂,但它会在应用程序的通信基础框架中,不应经常更改。

AnyHashable

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

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

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 }
}

测试

// 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
    }
}

仅此而已。我希望这可能会有所帮助。 欢迎任何更正或评论。

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