斯威夫特等式协议

我不认为这可以做到,但我还是要问。我有一个协议:

protocol X {}

还有一个班:

class Y:X {}

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

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

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

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

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

Swift 不喜欢这样,因为 EquatableSelf引用,它将不再允许我使用它作为类型。只是作为一个通用的参数。

那么,是否有人找到了一种方法,可以将操作符应用于协议,而不会使协议作为一种类型变得无法使用?

46643 次浏览

你必须实现一个 协议扩展束缚到你的类类型。在那个扩展中你应该实现 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")
}

但是我建议您将操作符实现添加到类中

也许这个对你有帮助:

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

让一个协议符合 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在逻辑上没有意义的信息。

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

不确定为什么需要协议的所有实例都符合 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()
}
}

通过这种方式,您可以保持规范的清晰,并让子类仅在需要时实现它们的相等方法。

我仍然建议不要使用多态性实现 ==。有点暗号的味道。如果你想给框架用户一些他可以用来测试相等性的东西,那么你真的应该出售一个 struct,而不是一个 protocol。但这并不是说不可能是 protocol公司在出售 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

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

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

请注意,由于类型橡皮擦符合协议,因此您可以在需要协议类型实例的任何地方使用类型橡皮擦的实例。

希望这个能帮上忙。

所有那些说你不能为一个协议实现 Equatable的人只是不够努力。以下是您的协议 X示例的解决方案(Swift 4.1) :

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
print(y == z) // true

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

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

确定符合 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)))
}


}

任何不符合 Equatable的一致性都需要自己实现 isEqual(to:)

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

// 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之类的行程,那么您必须将该代码包围在 // swiftlint:disable static_operator周围,使之成为无声的行程警告。

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

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.")
}

从上面获取了一些代码,并提供了以下解决方案。

它使用 Isequals 协议而不是 Equtable 协议只需要几行代码你就可以比较任意两个协议对象不管它们是否是可选的它们都在一个数组中甚至可以添加比较日期。

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


func == (lhs: IsEqual?, rhs: IsEqual?) -> Bool {
guard let lhs = lhs else { return rhs == nil }
guard let rhs = rhs else { return false }
return lhs.isEqualTo(rhs) }


func == (lhs: [IsEqual]?, rhs: [IsEqual]?) -> Bool {
guard let lhs = lhs else { return rhs == nil }
guard let rhs = rhs else { return false }
    

guard lhs.count == rhs.count else { return false }
for i in 0..<lhs.count {
if !lhs[i].isEqualTo(rhs[i]) {
return false
}
}
return true
}


func == (lhs: Date?, rhs: Date?) -> Bool {
guard let lhs = lhs else { return rhs == nil }
guard let rhs = rhs else { return false }
    

return lhs.compare(rhs) == .orderedSame
}


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