[Swift] Standard Library の Equatable を理解する

     
⌛️ 2 min.

Standard Library の Protocol である Equatable, Comparable, Identifiable, Hashable を順番に説明していこうと思います。第1回は、Equatable です。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS14.5
  • Xcode 15.4
  • iOS 17.5
  • Swift 5.9

Foundation Protocol シリーズ

Standard Library の Protocol 説明シリーズ
[Swift] Standard Library の Equatable を理解する [Swift] Standard Library の Comparable を理解する

今後の予定: Hashable, Identifiable

Protocol Equatable

Equatable というのは、Swift の Standard Library が用意している Protocol の1つです。


参考
EquatableApple Developer Documentation

Protocol Equatable とは

Equatable に conform するということは、その名の通り、同値判定できる型であることを表明しているということです。

Equate (同一視する) + able (可能にする) = Equatable です。

Swift の Standard Library では、Collection でのメソッドによく使用されています。

例えば、ある集合があって、その集合に特定の要素がふくまれているかどうか をチェックするということは、その集合にふくまれているいずれかの要素が 特定の要素と同値であるかどうか をチェックするということになります。
Collection の1つである Array には、以下のようなメソッドが用意されていて、ある要素が その Array に含まれているかどうかを判定できます。条件は、Element が Equatable に conform していることです。


func contains(_ element: Self.Element) -> Bool
Available when Element conforms to Equatable.

Equatable を使用して、同値判定をしていると思われます。そのままですね。

Protocol が要求するメソッド

次に、Equatable という Protocol に conform させるとは、どういうことかを確認します。

Equatable に conform させるとは、以下のメソッドを定義することです。

static func == (lhs: Self, rhs: Self) -> Bool

Equatable は、struct/ enum/ class/ actor に対して適用することができます。

actor に適用するときの 注意

多くのケースでは同値判定する時に、それぞれのプロパティ同士の比較を用いますが、上記の static メソッドは、同期的(sync) であることが必要です。
言い換えると、actor isolated されているプロパティを比較に使用するために、上記を 非同期(async) なメソッドとして定義しても、Equatable に conform したことにはなりません。
Protocol が要求するのは、同期的(sync) な static メソッドです。

指定メソッドを定義することで、conform させることができるので、extension で定義することでも conform させることもできます。

すでに Equatable 準拠している型とその組み合わせ

Swift の基本的な型の大半は、Equatable に conform しています。

ですので、Double, Int, String 等もすでに Equatable です。具体的な型が Equatable に conform しているかは、ドキュメントを確認してください。

Equatable な stored property/associated value を持つ struct/enum の扱い

Equatable に conform している型を stored property として持っている struct や associated value として持っている enum については、Equatable に conform していることを宣言すると、追加実装なしに Equatable に conform することができます。

なお、その際には、すべての stored property や associated value が同値であることが、struct/ enum が同値と判定される条件となります。

Equatable が満たすべき条件

Equatable で同値と判定される条件として、大抵は、同じ値であることがその条件になるかと思いますが、Swift 言語としてそのような制約があるわけではありません。

Equatable での判定が決定的でなくなってしまうことが考えられるため、Equatable に conform するための判定としては、以下を満たすことが必要です。

・a == a は、常に成り立つこと (反射律)
・a == b であるとき、b == a も成り立つこと (対象律)
・a == b でかつ b == c であるとき、a == c も成り立つこと (推移律)

デフォルトの実装を使用している時は問題ありませんが、自分で 少しトリッキーな == を実装するときには気をつけないといけません。

独自実装してみる

以下のような struct を独自実装で、Equatable に conform させてみます。

struct Person {
    var familyName: String?
    var givenName: String?
}

よくある(?) 氏名を持つ struct ですが、不明な場合に省略できるように Optional にしています。

省略されている部分は同値でないものとして 同値判定する == を定義してみます。

extension Person: Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        Self.sameName(lhs: lhs.familyName, rhs: rhs.familyName) && Self.sameName(lhs: lhs.givenName, rhs: rhs.givenName)
    }

    static func sameName(lhs: String?, rhs: String?) -> Bool {
        if let lhsName = lhs,
           let rhsName = rhs {
            return lhsName == rhsName
        }
        return false
    }
}

ちょっと適当な同値関係ですが、先ほどの 満たすべき条件は、ちゃんと 満たされています。

以下のように実行してみることで、期待通り(?)に動作していることも確認できます。

import Foundation

struct Person {
    var familyName: String?
    var givenName: String?
}

extension Person: Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        Self.sameName(lhs: lhs.familyName, rhs: rhs.familyName) && Self.sameName(lhs: lhs.givenName, rhs: rhs.givenName)
    }

    static func sameName(lhs: String?, rhs: String?) -> Bool {
        if let lhsName = lhs,
           let rhsName = rhs {
            return lhsName == rhsName
        }
        return false
    }
}

let suzukiIchiro = Person(familyName: "Suzuki", givenName: "Ichiro")
let tsunodaYuki = Person(familyName: "Tsunoda", givenName: "Yuki")
let ichiroKun = Person(familyName: nil, givenName: "Ichiro")
let suzukiSan = Person(familyName: "Suzuki", givenName: nil)
let johnDoe = Person(familyName: nil, givenName: nil)
let anotherSuzukiIchiro = Person(familyName: "Suzuki", givenName: "Ichiro")

print(suzukiIchiro == suzukiSan)     // false
print(suzukiIchiro == ichiroKun)     // false
print(suzukiIchiro == tsunodaYuki)   // false
print(tsunodaYuki == suzukiSan)      // false
print(suzukiIchiro == johnDoe)       // false
print(tsunodaYuki == johnDoe)        // false
print(suzukiIchiro == anotherSuzukiIchiro)     // true

NSObject の Equatable

NSObject は、Equatable に準拠しています。同値判定の基準は 「同じインスタンスかどうか」 です。

内部的には、isEqual(to: Any?) を使用していますので、挙動を変更したい場合は override することが必要です。

まとめ

Standard Library の Equatable

Standard Library の Equatable
  • 同値関係を確認できることを表明している Protocol
  • Swift で提供される型の大半は conform している
  • 自分で conform させるときは、static func ==(lhs:Self, rhs:Self) を定義する
  • 同値関係は、反射律/ 対象律/ 推移律 が成立しないといけない
  • struct の stored property/ enum の associated value が Equatable であれば、struct/ enum を Equatable に conform させるだけで実装なしに conform できる

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI ViewMatery

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。

英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です