ios Swift Equatable 在协议上

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42130150/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 08:51:54  来源:igfitidea点击:

Swift Equatable on a protocol

iosswiftprotocolsequatable

提问by drekka

I don't think this can be done but I'll ask anyway. I have a protocol:

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

protocol X {}

And a class:

还有一个类:

class Y:X {}

In the rest of my code I refer to everything using the protocol X. In that code I would like to be able to do something like:

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

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

The problem is that if I try to implement Equatable:

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

The idea to try and allow the use of ==whilst hiding the implementations behind the protocol.

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

Swift doesn't like this though because Equatablehas Selfreferences and it will no longer allow me to use it as a type. Only as a generic argument.

Swift 不喜欢这个,因为它EquatableSelf引用并且它不再允许我将它用作类型。仅作为通用参数。

So has anyone found a way to apply an operator to a protocol without the protocol becoming unusable as a type?

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

回答by Khawer Khaliq

If you directly implement Equatableon a protocol, it will not longer be usable as a type, which defeats the purpose of using a protocol. Even if you just implement ==functions on protocols without Equatableconformance, results can be erroneous. See this post on my blog for a demonstration of these issues:

如果直接Equatable在协议上实现,它将不再可用作类型,这违背了使用协议的目的。即使您只是==在没有Equatable一致性的协议上实现功能,结果也可能是错误的。有关这些问题的演示,请参阅我博客上的这篇文章:

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

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

The approach that I have found to work best is to use type erasure. This allows making ==comparisons for protocol types (wrapped in type erasers). It is important to note that while we continue to work at the protocol level, the actual ==comparisons are delegated to the underlying concrete types to ensure correct results.

我发现最有效的方法是使用类型擦除。这允许==对协议类型进行比较(包装在类型橡皮擦中)。需要注意的是,虽然我们继续在协议级别工作,但实际==比较被委托给底层的具体类型以确保正确的结果。

I have built a type eraser using your brief example and added some test code at the end. I have added a constant of type Stringto the protocol and created two conforming types (structs are the easiest for demonstration purposes) to be able to test the various scenarios.

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

For a detailed explanation of the type erasure methodology used, check out part two of the above blog post:

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

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

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

The code below should support the equality comparison that you wanted to implement. You just have to wrap the protocol type in a type eraser instance.

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

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

Note that since the type eraser conforms to the protocol, you can use instances of the type eraser anywhere an instance of the protocol type is expected.

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

Hope this helps.

希望这可以帮助。

回答by Scott H

The reason why you should think twice about having a protocol conform to Equatableis that in many cases it just doesn't make sense. Consider this example:

您应该三思而后行让协议符合的原因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??")
}

You allude to type checking within the implementation of ==but the problem is that you have no information about either of the types beyond them being Pets and you don't know all the things that might be a Pet(maybe you will add a Birdand Rabbitlater). If you really need this, another approach can be modeling how languages like C# implement equality, by doing something like:

你暗示类型的实现内检查==,但问题是,你有关于任何类型的超越他们暂时无信息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.")
}

At which point if you really wanted, you could implement ==without implementing Equatable:

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

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

One thing you would have to watch out for in this case is inheritance though. Because you could downcast an inheriting type and erase the information that might make isEqualTonot make logical sense.

在这种情况下,您必须注意的一件事是继承。因为您可以向下转换继承类型并删除可能isEqualTo没有逻辑意义的信息。

The best way to go though is to only implement equality on the class/struct themselves and use another mechanism for type checking.

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

回答by Konstantin Kryzhanovsky

maybe this will be helpful for you:

也许这对你有帮助:

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

回答by redent84

Not sure why you need all instances of your protocol to conform to Equatable, but I prefer letting classes implement their equality methods.

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

In this case, I'd leave the protocol simple:

在这种情况下,我会让协议保持简单:

protocol MyProtocol {
    func doSomething()
}

If you require that an object that conforms to MyProtocolis also Equatableyou can use MyProtocol & Equatableas type constraint:

如果您要求符合的对象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()
    }
}


This way you can keep your specification clear and let subclasses implement their equality method only if required.

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

回答by Scott H

I would still advise against implementing ==using polymorphism. It's a bit of a code smell. If you want to give the framework user something he can test equality with then you should really be vending a struct, not a protocol. That's not to say that it can't be the protocols that are vending the structs though:

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

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

I think this more efficiently communicates your intent, which is basically "you have these things and you don't know if they are the same things, but you do know they have the same set of properties and you can test whether those properties are the same." That is pretty close to how I would implement that Moneyexample.

我认为这更有效地传达了您的意图,这基本上是“您拥有这些东西,但您不知道它们是否相同,但您确实知道它们具有相同的属性集,并且您可以测试这些属性是否是相同的。” 这与我将如何实现该Money示例非常接近。

回答by Jaysen Marais

Determining equality across conformances to a Swift protocol is possible without type erasureif:

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

  • you are willing to forgo the operator syntax (i.e. call isEqual(to:)instead of ==)
  • you control the protocol (so you can add an isEqual(to:)func to it)
  • 您愿意放弃运算符语法(即调用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({ 
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
}
== 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))) } }

Any conformances don't conform to Equatablewill need to implement isEqual(to:)themselves

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

回答by kelin

All people who say that you can't implement Equatablefor a protocol just don't try hard enough. Here is the solution (Swift 4.1) for your protocol Xexample:

所有说你不能Equatable为协议实现的人只是不够努力。这是您的协议示例的解决方案(Swift 4.1X

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

And it works!

它有效!

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

The only problem is that you can't write let a: X = Y()because of "Protocol can only be used as a generic constraint"error.

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

回答by ozmpai

Swift 5.1 introduces a new feature into the language called opaque types
Check code below
that still gets back a?X, which might be an?Y, a?Z, or something else that conforms to the?X?protocol,
but the?compiler knows exactly what is being returned

Swift 5.1 在语言中引入了一个名为 opaque types 的新特性
检查下面的代码
仍然返回 a?X,它可能是一个?Y、a?Z 或其他符合?X? 协议的东西,
但是?编译器确切地知道返回的是什么

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

回答by Adolfo

You have to implement a protocol extensionconstrainedto your class type. Inside that extension you should implement the Equatableoperator.

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

##代码##

But let me recommend you add the operator implementation to your class. Keep It Simple ;-)

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