web-dev-qa-db-ja.com

Swift:サブクラスで==をオーバーライドすると、スーパークラスでのみ==が呼び出される

Aプロトコルに準拠し、==関数を実装するクラスEquatableを持っています。サブクラスBでは、==をより多くのチェックでオーバーライドしています。

ただし、Bのインスタンスの2つの配列(どちらもArray<A>の型を持つ)を比較すると、A==が呼び出されます。もちろん、両方の配列のタイプをArray<B>に変更すると、B==が呼び出されます。

私は次の解決策を思いつきました:

A.Swift:

internal func ==(lhs: A, rhs: A) -> Bool {
    if lhs is B && rhs is B {
        return lhs as! B == rhs as! B
    }
    return ...
}

これは本当に醜く見え、Aのすべてのサブクラスに対して拡張する必要があります。サブクラスの==が最初に呼び出されることを確認する方法はありますか?

29
kovpas

Aを含むArray<A>に対してBの等価性が呼び出されるのは、フリー関数のオーバーロードが動的ではなく静的に解決されるためです。つまり、コンパイル時に型ではなく、ポイントされた値に基づく実行時ではありません。

==がクラス内で宣言されておらず、サブクラスでオーバーライドされている場合、これは当然のことです。従来のOOの手法を使用して多態性の同等性を定義することは非常に制限的であるように思われるかもしれませんが、 このリンク および この論文)を参照してください 詳細については。

単純な解決策は、動的にディスパッチされる関数をAで定義し、次に==を定義してそれを呼び出すことです。

class A: Equatable {
    func equalTo(rhs: A) -> Bool {
        // whatever equality means for two As
    }
}

func ==(lhs: A, rhs: A) -> Bool {
    return lhs.equalTo(rhs)
}

次に、Bを実装すると、equalToをオーバーライドします。

class B: A {
    override func equalTo(rhs: A) -> Bool {
        return (rhs as? B).map { b in
            return // whatever it means for two Bs to be equal
        } ?? false   // false, assuming a B and an A can’t be Equal
    }
}

右側の引数がBであるかどうかを判別する必要があるため、1つのas?ダンスを実行する必要があります(equalToBを直接受け取った場合、正当なオーバーライドではありません)。

また、ここにはまだ驚くべき動作が隠されています。

let x: [A] = [B()]
let y: [A] = [A()]

// this runs B’s equalTo
x == y
// this runs A’s equalTo
y == x

つまり、引数の順序によって動作が変わります。これは良くありません。人々は平等が対称的であることを期待しています。したがって、実際にこれを適切に解決するには、上記のリンクで説明されているいくつかの手法が必要です。

この時点で、これらすべてが少し不要になったように感じるかもしれません。そして、おそらく、特にSwift標準ライブラリのEquatableに関するドキュメントで次のコメントが与えられています:

平等は代入性を意味します。 x == yxおよびyは、それらの値にのみ依存するコードでは交換可能です。

トリプルイコール===で区別されるクラスインスタンスIDは、特にインスタンスの値の一部ではありません。 Equatable型の他の非値の側面を公開することはお勧めしません。公開されているは、ドキュメントで明示的に指摘する必要があります。

このことを考慮して、同等性を実装している方法がではない場合に、Equatable実装の空想を得ることを真剣に考え直す必要があるかもしれません。等しい2つの値が互いに置き換えられて満足します。これを回避する1つの方法は、オブジェクトの同一性を等価性の尺度と見なし、=====として実装することです。これは、スーパークラスに対して1回だけ実行する必要があります。あるいは、あなた自身に尋ねることができます、あなたは本当に実装継承を必要としますか?そうでない場合は、それを破棄し、代わりに値型を使用して、プロトコルとジェネリックを使用して、探している多態的な動作をキャプチャすることを検討してください。

41

MKPointAnnotationサブクラス(NSObjectから継承)でdifference(from:)を使用したかったため、同様の問題に遭遇しました。 _func ==_を注釈サブクラスに追加しても、_difference(from:_はNSObjectの_==_の実装を呼び出します。これはちょうど2つのオブジェクトのメモリの場所を比較していると思います。 _difference(from:_を正しく機能させるには、注釈サブクラスにoverride func isEqual(_ object: Any?) -> Bool {を実装する必要がありました。本文では、objectがサブクラスと同じ型であることを確認してから、そこで比較します。

0
Tylerc230