【问题标题】:Swift Declare a protocol equatableSwift 声明一个可等价的协议
【发布时间】:2017-08-13 11:12:01
【问题描述】:

我有一个协议:

protocol CustomProtocol {
   var title: String { get }
   var subtitle: String { get }
}

然后我有 2 个符合此协议的对象。我想比较它们,所以我希望 CustomProtocol 是 Equatable。

protocol CustomProtocol: Equatable {
   var title: String { get }
   var subtitle: String { get }
    static func ==(lhs: Self, rhs: Self) -> Bool
}

extension CustomProtocol {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs.title == rhs.title
    }
}

但在那次更改之后,我得到“协议 CustomProtocol 只能用作通用约束,因为它具有 Self 或关联的类型要求。 我能想到解决这个问题的唯一方法是拥有第三个属性,比如依赖于其他属性的哈希,然后比较这个属性。

Here you have a sample playground 和实际代码。

【问题讨论】:

标签: swift


【解决方案1】:

因为EquatableSelf 要求,所以不应该直接在协议上实现。否则,协议将无法用作类型。

要在协议级别实现Equatable 并能够将协议用作类型,您可以使用类型擦除。

为了演示,我修改了你的 Playground 中给出的代码来构建一个类型橡皮擦。

有关我使用的方法的详细说明,请查看我博客上的这篇文章:

https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/

这是您的 Playground 中修改后的代码:

protocol CustomProtocol {
    var title: String { get }
    var subtitle: String { get }
    func isEqualTo(_ other: CustomProtocol) -> Bool
    func asEquatable() -> AnyEquatableCustomProtocol
}

extension CustomProtocol where Self: Equatable {
    func isEqualTo(_ other: CustomProtocol) -> Bool {
        guard let o = other as? Self else { return false }
        return self == o
    }
    func asEquatable() -> AnyEquatableCustomProtocol {
        return AnyEquatableCustomProtocol(self)
    }
}

struct A: CustomProtocol, Equatable {
    var title: String
    var subtitle: String
    static func ==(lhs: A, rhs: A) -> Bool {
        return lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
    }
}

struct B: CustomProtocol, Equatable {
    var title: String
    var subtitle: String
    static func ==(lhs: B, rhs: B) -> Bool {
        return lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
    }
}

struct AnyEquatableCustomProtocol: CustomProtocol, Equatable {
    var title: String { return value.title }
    var subtitle: String { return value.subtitle }
    init(_ value: CustomProtocol) { self.value = value }
    private let value: CustomProtocol
    static func ==(lhs: AnyEquatableCustomProtocol, rhs: AnyEquatableCustomProtocol) -> Bool {
        return lhs.value.isEqualTo(rhs.value)
    }
}

// instances typed as the protocol
let a: CustomProtocol = A(title: "First title", subtitle: "First subtitle")
let b: CustomProtocol = B(title: "First title", subtitle: "First subtitle")
let equalA: CustomProtocol = A(title: "First title", subtitle: "First subtitle")
let unequalA: CustomProtocol = A(title: "Second title", subtitle: "Second subtitle")

// equality tests
print(a.asEquatable() == b.asEquatable())           // prints false
print(a.asEquatable() == equalA.asEquatable())      // prints true
print(a.asEquatable() == unequalA.asEquatable())    // prints false

需要注意的是,通过这种方法,实际的== 比较被委托给底层的具体类型,但我们只处理协议类型以保持抽象。

这里我只使用类型擦除实例进行比较。但是,由于类型擦除器符合CustomProtocol,这些实例可以保存并在任何需要协议类型的地方使用。因为它们符合Equatable,所以它们也可以用于任何需要Equatable 一致性的地方。

就上下文而言,这篇文章解释了为什么不建议尝试直接在协议上实现Equatable 一致性甚至== 函数:

https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/

因此类型擦除。

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    Equatable 协议有一个自我约束来解决您只能检查相同类型的对象之间的相等性问题,而不是相同的协议。这就是为什么它有一个自我要求。否则你只能说

    let a: Equatable = 42
    let b: Equatable = "hello"
    

    a == b 会起作用。这会很糟糕,因为您可以比较完全不相关类型的对象。自我要求使这成为编译时错误。

    如果您想在协议基础上比较您的对象,只需实现 == 运算符,无需自我要求:

    extension CustomProtocol {
        func == (lhs: CustomProtocol, rhs: CustomProtocol) -> Bool {
             return lhs.name == rhs.name
        }
        func != (lhs: CustomProtocol, rhs: CustomProtocol) -> Bool {
             return !(lhs == rhs)
        }
    }
    

    现在您可以直接使用CustomProtocol 类型声明您的协议实例并进行比较。

    但在这种情况下,协议可能不是正确的抽象。也许你应该把它实现为一个抽象类。

    【讨论】:

    • 我得到“对 == 的模糊引用”
    • 当然你的协议不能再继承自Equatable
    • 我添加了一个示例 Playground,删除继承后相同。
    • 您可以尝试将==!= 函数放入全局范围而不是扩展。
    【解决方案3】:

    问题是rhs参数和lhs的类型不一样。它们只是遵循相同的协议。

    您可以通过使用泛型类型作为第二个参数来解决这个问题。

    exension CustomProtocol {
        static func ==<T: CustomProtocol>(lhs: Self, rhs: T) -> Bool {
            return lhs.title == rhs.title
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-08-02
      • 2015-12-18
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-15
      相关资源
      最近更新 更多