Swift Equatable on a protocol

dre*_*kka 36 protocols ios swift equatable

我不认为这可以做,但无论如何我都会问.我有一个协议:

protocol X {}
Run Code Online (Sandbox Code Playgroud)

一节课:

class Y:X {}
Run Code Online (Sandbox Code Playgroud)

在我的其余代码中,我使用协议X引用所有内容.在该代码中,我希望能够执行以下操作:

let a:X = ...
let b:X = ...
if a == b {...}
Run Code Online (Sandbox Code Playgroud)

问题是,如果我尝试实现Equatable:

protocol X: Equatable {}
func ==(lhs:X, hrs:X) -> Bool {
    if let l = lhs as? Y, let r = hrs as? Y {
        return l.something == r.something
    }
    return false
} 
Run Code Online (Sandbox Code Playgroud)

尝试允许使用==隐藏协议背后的实现的想法.

Swift不喜欢这个,因为它EquatableSelf引用,它将不再允许我将它用作类型.仅作为一般参数.

那么有没有人找到一种方法将一个运算符应用于一个协议,而协议不会作为一种类型变得无法使用?

Kha*_*liq 20

如果直接Equatable在协议上实现,它将不再可用作类型,这会破坏使用协议的目的.即使您只是==在协议上实现不一致的函数Equatable,结果也可能是错误的.请在我的博客上查看此帖子,以了解这些问题:

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

我发现最好的方法是使用类型擦除.这允许==对协议类型进行比较(包裹在类型擦除器中).值得注意的是,当我们继续在协议级别工作时,实际的==比较被委托给底层的具体类型以确保正确的结果.

我使用您的简短示例构建了一个类型橡皮擦,并在最后添加了一些测试代码.我已经String为协议添加了一个类型的常量,并创建了两个符合要求的类型(结构是最容易用于演示的目的),以便能够测试各种场景.

有关所使用的类型擦除方法的详细说明,请查看上述博客文章的第二部分:

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

下面的代码应该支持您要实现的相等比较.您只需将协议类型包装在类型擦除器实例中.

protocol X {
    var name: String { get }
    func isEqualTo(_ other: X) -> Bool
    func asEquatable() -> AnyEquatableX
}

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

struct Y: X, Equatable {
    let name: String
    static func ==(lhs: Y, rhs: Y) -> Bool {
        return lhs.name == rhs.name
    }
}

struct Z: X, Equatable {
    let name: String
    static func ==(lhs: Z, rhs: Z) -> Bool {
        return lhs.name == rhs.name
    }
}

struct AnyEquatableX: X, Equatable {
    var name: String { return value.name }
    init(_ value: X) { self.value = value }
    private let value: X
    static func ==(lhs: AnyEquatableX, rhs: AnyEquatableX) -> Bool {
        return lhs.value.isEqualTo(rhs.value)
    }
}

// instances typed as the protocol
let y: X = Y(name: "My name")
let z: X = Z(name: "My name")
let equalY: X = Y(name: "My name")
let unequalY: X = Y(name: "Your name")

// equality tests
print(y.asEquatable() == z.asEquatable())           // prints false
print(y.asEquatable() == equalY.asEquatable())      // prints true
print(y.asEquatable() == unequalY.asEquatable())    // prints false
Run Code Online (Sandbox Code Playgroud)

请注意,由于类型橡皮擦符合协议,因此您可以在期望协议类型的实例的任何位置使用类型橡皮擦的实例.

希望这可以帮助.


Jay*_*ais 14

在以下情况下,可以在不进行类型擦除的情况确定与 Swift 协议的一致性:

  • 您愿意放弃运算符语法(即调用isEqual(to:)而不是==
  • 你控制协议(所以你可以isEqual(to:)向它添加一个函数)
import XCTest

protocol Shape {
    func isEqual (to: Shape) -> Bool
}

extension Shape where Self : Equatable {
    func isEqual (to: Shape) -> Bool {
        return (to as? Self).flatMap({ $0 == self }) ?? false
    }
}

struct Circle : Shape, Equatable {
    let radius: Double
}

struct Square : Shape, Equatable {
    let edge: Double
}

class ProtocolConformanceEquality: XCTestCase {

    func test() {
        // Does the right thing for same type
        XCTAssertTrue(Circle(radius: 1).isEqual(to: Circle(radius: 1)))
        XCTAssertFalse(Circle(radius: 1).isEqual(to: Circle(radius: 2)))

        // Does the right thing for different types
        XCTAssertFalse(Square(edge: 1).isEqual(to: Circle(radius: 1)))
    }

}
Run Code Online (Sandbox Code Playgroud)

任何不合格不符合Equatable需要实现isEqual(to:)自己

  • 这是从 Swift 5.1 开始的最佳答案 - 如果提供另一个示例,例如“isEqual(to:)”将为除“Shape”之外的其他协议提供默认实现,那就更好了 (2认同)

Sco*_*t H 8

你应该三思而后行的原因Equatable是,在很多情况下它只是没有意义.考虑这个例子:

protocol Pet: Equatable {
  var age: Int { get }
}

extension Pet {
  static func == (lhs: Pet, rhs: Pet) -> Bool {
    return lhs.age == rhs.age
  }
}

struct Dog: Pet {
  let age: Int
  let favoriteFood: String
}

struct Cat: Pet {
  let age: Int
  let favoriteLitter: String
}

let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")

if rover == simba {
  print("Should this be true??")
}
Run Code Online (Sandbox Code Playgroud)

你暗示类型的实现内检查==,但问题是,你有没有关于任何超越他们是信息的类型PetS和你不知道所有这可能是一个东西Pet(也许你会添加BirdRabbit更新版本).如果你真的需要这个,另一种方法可以通过做类似的事情来模拟像C#这样的语言如何实现相等:

protocol IsEqual {
  func isEqualTo(_ object: Any) -> Bool
}

protocol Pet: IsEqual {
  var age: Int { get }
}

struct Dog: Pet {
  let age: Int
  let favoriteFood: String

  func isEqualTo(_ object: Any) -> Bool {
    guard let otherDog = object as? Dog else { return false }

    return age == otherDog.age && favoriteFood == otherDog.favoriteFood
  }
}

struct Cat: Pet {
  let age: Int
  let favoriteLitter: String

  func isEqualTo(_ object: Any) -> Bool {
    guard let otherCat = object as? Cat else { return false }

    return age == otherCat.age && favoriteLitter == otherCat.favoriteLitter
  }
}

let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")

if !rover.isEqualTo(simba) {
  print("That's more like it.")
}
Run Code Online (Sandbox Code Playgroud)

在这一点上,如果你真的想要,你可以实现==而不实现Equatable:

static func == (lhs: IsEqual, rhs: IsEqual) -> Bool { return lhs.isEqualTo(rhs) }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,你需要注意的一件事是继承.因为您可以向下转换继承类型并删除可能isEqualTo使逻辑不合理的信息.

最好的方法是只在类/结构本身上实现相等,并使用另一种机制进行类型检查.


小智 6

也许这对您有帮助:

protocol X:Equatable {
    var name: String {get set}

}

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

struct Test : X {
    var name: String
}

let first = Test(name: "Test1")
let second = Test(name: "Test2")

print(first == second) // false
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,但这是可行的,直到您尝试执行类似“ let first = Test(name:” Test1“)as X”之类的操作,然后您才收到关于不允许'X'作为类型的错误。我的问题是我的类深入API,并且只能通过协议公开。因此,这些实例的用法必须是协议类型。 (5认同)

red*_*t84 5

不确定为什么需要协议的所有实例都符合Equatable,但我更喜欢让类实现它们的相等方法.

在这种情况下,我会让协议变得简单:

protocol MyProtocol {
    func doSomething()
}
Run Code Online (Sandbox Code Playgroud)

如果您要求符合的对象MyProtocolEquatable可以MyProtocol & Equatable用作类型约束:

// Equivalent: func doSomething<T>(element1: T, element2: T) where T: MyProtocol & Equatable {
func doSomething<T: MyProtocol & Equatable>(element1: T, element2: T) {
    if element1 == element2 {
        element1.doSomething()
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,您可以保持规范清晰,并且只有在需要时才让子类实现它们的相等方法.


kel*_*lin 5

所有说无法实现Equatable协议的人只是没有足够努力。这是您的协议示例的解决方案(Swift 4.1X ) :

protocol X: Equatable {
    var something: Int { get }
}

// Define this operator in the global scope!
func ==<L: X, R: X>(l: L, r: R) -> Bool {
    return l.something == r.something
}
Run Code Online (Sandbox Code Playgroud)

它有效!

class Y: X {
    var something: Int = 14
}

struct Z: X {
    let something: Int = 9
}

let y = Y()
let z = Z()
print(y == z) // false

y.something = z.something
print(y == z) // true
Run Code Online (Sandbox Code Playgroud)

唯一的问题是你无法编写,let a: X = Y()因为“协议只能用作通用约束”错误。


Ado*_*lfo 2

您必须实现限制于您的类类型的协议扩展。 在该扩展中,您应该实现运算符。Equatable

public protocol Protocolable: class, Equatable
{
    // Other stuff here...
}

public extension Protocolable where Self: TheClass
{
    public static func ==(lhs: Self, rhs:Self) -> Bool 
    {
        return lhs.name == rhs.name
    } 
}


public class TheClass: Protocolable
{
    public var name: String

    public init(named name: String)
    {
        self.name = name
    }
}

let aClass: TheClass = TheClass(named: "Cars")
let otherClass: TheClass = TheClass(named: "Wall-E")

if aClass == otherClass
{
    print("Equals")
}
else
{
    print("Non Equals")
}
Run Code Online (Sandbox Code Playgroud)

但我建议您将运算符实现添加到您的类中。把事情简单化 ;-)