Swift Equatable on a protocol

时间:2017-02-09 06:55:05

标签: ios swift protocols equatable

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

protocol X {}

一堂课:

class Y:X {}

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

let a:X = ...
let b:X = ...
if a == b {...}

问题在于,如果我尝试实施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
} 

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

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

那么有没有人找到一种方法将操作符应用于协议而不会使协议变为不可用的类型?

9 个答案:

答案 0 :(得分:17)

如果直接在协议上实现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

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

希望这会有所帮助。

答案 1 :(得分: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??")
}

您提到在==的实施中进行类型检查,但问题是您没有关于Pet以外的任何类型的信息,并且您不知道所有可能是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.")
}

如果您真的想要,可以在不实施==的情况下实施Equatable

static func == (lhs: IsEqual, rhs: IsEqual) -> Bool { return lhs.isEqualTo(rhs) }

在这种情况下,您需要注意的一件事是继承。因为您可以转发继承类型并删除可能使isEqualTo无法理解的信息。

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

答案 2 :(得分:6)

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

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

protocol MyProtocol {
    func doSomething()
}

如果您要求符合MyProtocol的对象也是Equatable,则可以使用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()
    }
}

通过这种方式,您可以保持规范清晰,并且只有在需要时才允许子类实现其相等方法。

答案 3 :(得分:4)

也许这会对你有所帮助:

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

答案 4 :(得分:3)

我仍然建议不要使用多态来实现==。它有点代码味道。如果你想给框架用户一些他可以测试相等性的东西,那么你应该真正出售struct,而不是protocol。这并不是说它不能成为出售protocol s的struct

struct Info: Equatable {
  let a: Int
  let b: String

  static func == (lhs: Info, rhs: Info) -> Bool {
    return lhs.a == rhs.a && lhs.b == rhs.b
  }
}

protocol HasInfo {
  var info: Info { get }
}

class FirstClass: HasInfo {
  /* ... */
}

class SecondClass: HasInfo {
  /* ... */
}

let x: HasInfo = FirstClass( /* ... */ )
let y: HasInfo = SecondClass( /* ... */ )

print(x == y) // nope
print(x.info == y.info) // yep

我认为这可以更有效地传达你的意图,这基本上就是#34;你有这些东西,你不知道它们是否是相同的东西,但你知道它们具有相同的属性和你可以测试这些属性是否相同。&#34;这与我实现Money示例的方式非常接近。

答案 5 :(得分:1)

您必须为您的班级类型实施协议扩展 约束。在该扩展程序中,您应该实现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")
}

但是,我建议您在您的班级中添加运算符实现。保持简单; - )

答案 6 :(得分:1)

Swift 5.1将一种新功能引入了称为不透明类型的语言中
检查下面的代码
仍然会返回X(可能是Y,Z或其他符合X协议的东西),
但是编译器确切地知道返回了什么

protocol X: Equatable { }
class Y: X {
    var something = 3
    static func == (lhs: Y, rhs: Y) -> Bool {
        return lhs.something == rhs.something
    }
    static func make() -> some X {
        return Y() 
    }
}
class Z: X {
    var something = "5"
    static func == (lhs: Z, rhs: Z) -> Bool {
        return lhs.something == rhs.something
    }
    static func make() -> some X {
        return Z() 
    }
}



let a = Z.make()
let b = Z.make()

a == b

答案 7 :(得分:1)

我遇到了同样的问题,我认为 == 运算符可以在全局范围内实现(就像以前一样),而不是协议范围内的静态函数:

// This should go in the global scope

public func == (lhs: MyProtocol?, rhs: MyProtocol?) -> Bool { return lhs?.id == rhs?.id }
public func != (lhs: MyProtocol?, rhs: MyProtocol?) -> Bool { return lhs?.id != rhs?.id }

请注意,如果您使用诸如 SwiftLint 的 static_operator 之类的 linter,则必须将该代码包裹在 // swiftlint:disable static_operator 周围以发出静默的 linter 警告。

然后这段代码将开始编译:

let obj1: MyProtocol = ConcreteType(id: "1")
let obj2: MyProtocol = ConcreteType(id: "2")
if obj1 == obj2 {
    print("They're equal.")
} else {
    print("They're not equal.")
}

答案 8 :(得分:0)

所有说您无法为协议实现$sponsors = $this->Sponsor->find('all', [ 'order' => [ 'Sponsor.name', ], 'conditions' => [ 'publish_from <' => date('Y-m-d'), 'publish_to >' => date('Y-m-d'), ], 'contain' => [ 'Activity' => [ 'conditions' => [ 'Activity.time_start >' => date('Y-m-d') ] ] ] ]); 的人都只是不够努力。这是您的协议Equatable示例的解决方案(Swift 4.1 ):

X

它有效!

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
}

唯一的问题是,由于“协议只能用作通用约束” 错误,您无法编写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 pirnt(y == z) // true