これができるとは思わないが、とにかく尋ねる。私はプロトコルを持っています:
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
}
プロトコルの背後に実装を隠しながら、==
の使用を試行および許可するというアイデア。
Equatable
にはSelf
参照があり、これを型として使用することはできなくなるため、Swiftはこれを好みません。一般的な引数としてのみ。
プロトコルが型として使用できなくなることなく、プロトコルに演算子を適用する方法を誰かが見つけましたか?
プロトコルにEquatable
を直接実装すると、タイプとして使用できなくなり、プロトコルを使用する目的に反します。 Equatable
に準拠せずにプロトコルに==
関数を実装しただけでも、結果が誤っている可能性があります。これらの問題のデモについては、私のブログのこの投稿を参照してください。
https://khawerkhaliq.com/blog/Swift-protocols-equatable-part-one/
私が最もうまくいくことがわかったアプローチは、型消去を使用することです。これにより、プロトコルタイプ(タイプイレーザーでラップ)の==
比較を行うことができます。プロトコルレベルでの作業を続けながら、実際の==
比較は、正しい結果を確実にするために、基礎となる具体的な型に委任されることに注意することが重要です。
簡単な例を使用してタイプイレーザーを作成し、最後にテストコードを追加しました。タイプString
の定数をプロトコルに追加し、さまざまなシナリオをテストできるように、2つの適合タイプ(構造はデモ目的で最も簡単です)を作成しました。
使用されるタイプ消去方法の詳細な説明については、上記のブログ投稿のパート2をご覧ください。
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
に準拠させることについて考え直す必要があるのは、多くの場合、意味をなさないためです。この例を考えてみましょう:
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
sである以外の型のいずれかに関する情報がなく、すべてのことを知らないことですPet
(後でBird
とRabbit
を追加する可能性があります)。これが本当に必要な場合、別のアプローチとして、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) }
ただし、この場合に注意しなければならないことの1つは、継承です。継承型をダウンキャストし、isEqualTo
が論理的に意味をなさない情報を消去できるためです。
しかし、最善の方法は、クラス/構造自体にのみ等価性を実装し、型チェックに別のメカニズムを使用することです。
多分これはあなたに役立つでしょう:
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 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
sを販売しているstruct
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
これは基本的に「これらのものがあり、同じものかどうかはわかりませんが、同じプロパティセットがあることはわかっているので、それらのプロパティが同じ。"これは、そのMoney
の例をどのように実装するかに非常に近いです。
protocol extensionconstrainedをクラスタイプに実装する必要があります。その拡張の中に、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")
}
ただし、クラスに演算子の実装を追加することをお勧めします。複雑にしないでおく ;-)
プロトコルに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
pirnt(y == z) // true
_
唯一の問題は、「プロトコルは汎用制約としてのみ使用できる」エラーのためにlet a: X = Y()
を記述できないことです。