文書   >   Swift   >   共通プロトコルの採用

記事


共通プロトコルの採用


Swift プロトコルに準拠していることを確認して、カスタム型を使いやすくします。





概要


プログラムでデータをモデル化するためにカスタム型を使用する場合、2 つの値が同じか異なるか、または特定の値が値のリストに含まれているかどうかを頻繁に確認する必要があります。この機能、および値を Set に格納したり、辞書内のキーとして使用したりする機能は、2 つの関連する標準ライブラリプロトコル EquatableHashable によって管理されています。


多くの標準ライブラリ型は、文字列、整数、浮動小数点値、ブール値、および equatable と hashable 型のコレクションを含め、共に equatable と hashable です。以下の例の == 比較と contains(_:) メソッドの呼び出しは、文字列と整数が equatable であることに依存しています。


if username == "Arturo" {
    print("Hi, Arturo!")
}

let favoriteNumbers = [4, 7, 8, 9]
if favoriteNumbers.contains(todaysDate.day) {
    print("It's a good day today!")
}


Equatable および Hashable プロトコルに準拠することは簡単で、Swift で独自の型を使用することをより簡単にします。すべてのカスタムモデル型が準拠することをお勧めします。


Equatable と Hashable に自動的に準拠


型の元の宣言と同じファイル内でこれらのプロトコルへの準拠を宣言するだけで、多くのカスタム型を Equatable と Hashable にすることができます。型を宣言するときに EquatableHashable を採用プロトコルのリストに追加すると、コンパイラは自動的に 2 つのプロトコルの要件を満たします。


/// A position in an x-y coordinate system.
struct Position: Equatable, Hashable {
    var x: Int
    var y: Int
    
    init(_ x: Int, _ y: Int) {
        self.x = x
        self.y = y
    }
}


Equatable への準拠では、Position 型の任意の 2 つのインスタンスで、等号演算子 (==) または不等号演算子 (!=) を使用できます。


let availablePositions = [Position(0, 0), Position(0, 1), Position(1, 0)]
let gemPosition = Position(1, 0)

for position in availablePositions {
    if gemPosition == position {
        print("Gem found at (\(position.x), \(position.y))!")
    } else {
        print("No gem at (\(position.x), \(position.y))")
    }
}
// No gem at (0, 0)
// No gem at (0, 1)
// Gem found at (1, 0)!


Hashable への準拠とは、以下の例に示すように、ポジションを Set に保存して、以前にそのポジションを訪問したことがあるかどうかをすばやく確認できることを意味します。


var visitedPositions: Set = [Position(0, 0), Position(1, 0)]
let currentPosition = Position(1, 3)

if visitedPositions.contains(currentPosition) {
    print("Already visited (\(currentPosition.x), \(currentPosition.y))")
} else {
    print("First time at (\(currentPosition.x), \(currentPosition.y))")
    visitedPositions.insert(currentPosition)
}
// First time at (1, 3)


コードの単純化に加えて、この自動的準拠によりエラーが減少します。と言うのも、カスタム型に追加した新しいプロパティは、ハッシュ化および等価性のテスト時に自動的に含まれるためです。これらの基準を満たす構造体または列挙型の場合、その型は Equatable および Hashable への自動準拠の対象となります。


Equatable と Hashable に手動で準拠


以下の場合には、型に対して EquatableHashable への準拠を手動で実装する必要があります。


class Player {
    var name: String
    var position: Position
    
    init(name: String, position: Position) {
        self.name = name
        self.position = position
    }
}


Player 型はクラスなので、Equatable または Hashable の要件の自動合成には適していません。このクラスを Equatable プロトコルに準拠させるには、拡張機能で準拠を宣言し、static == 演算子メソッドを実装します。== メソッドの実装で、各重要なプロパティが等しいかどうか比較します。


extension Player: Equatable {
    static func ==(lhs: Player, rhs: Player) -> Bool {
        return lhs.name == rhs.name && lhs.position == rhs.position
    }
}


PlayerHashable プロトコルに準拠させるには、別の拡張機能で準拠を宣言し、hash(into:) メソッドを実装します。hash(into:) メソッドで、それぞれの重要なプロパティを指定して、提供されたハッシャーで combine(_:) メソッドを呼び出します。


extension Player: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(position)
    }
}


Equatable と Hashable のすべての重要なプロパティを使用


== メソッドと hash(into:) メソッドを実装するときは、カスタム型の 2 つのインスタンスが等しいと見なされるかどうかに影響するすべてのプロパティを使用します。上記の実装では、Player 型は両方のメソッドで nameposition を使用しています。


2 つのインスタンスが等しいと見なされるかどうかに影響しないプロパティがあなたの型に含まれている場合は、それらのプロパティを == メソッドでの比較および hash(into:) でのハッシュから除外します。例えば、あるタイプは高価な計算値をキャッシュするため、一度計算するだけで済みます。その型の 2 つのインスタンスを比較する場合、計算された値がキャッシュされているかどうかは等価性に影響しないはずなので、キャッシュされた値は比較とハッシュから除外する必要があります。


重要

== メソッドと hash(into:) メソッドの両方で常に同じプロパティを使用してください。2 つのメソッドで異なるプロパティグループを使用すると、Set や辞書でカスタム型を使用するときに予期しない動作やパフォーマンスが発生する可能性があります。



NSObject サブクラスの動作をカスタマイズ


NSObject サブクラスは、インスタンスの同一性に基づく同等性で、Equatable および Hashable プロトコルへの準拠を継承します。この動作をカスタマイズする必要がある場合は、== 演算子メソッドと hashValue プロパティの代わりに isEqual(_:) メソッドと hash プロパティをオーバーライドします。


extension MyNSObjectSubclass {
    override func isEqual(_ object: Any?) -> Bool {
        guard let other = object as? MyNSObjectSubclass
            else { return false }
        return self.firstProperty == other.firstProperty 
            && self.secondProperty == other.secondProperty
    }

    override var hash: Int {
        var hasher = Hasher()
        hasher.combine(firstProperty)
        hasher.combine(secondProperty)
        return hasher.finalize()
    }
}


前のセクションで述べたように、等しいと考えられる 2 つのインスタンスは同じハッシュ値を持たなければなりません。これらの宣言の 1 つをオーバーライドする場合は、その保証を維持するために他の宣言もオーバーライドしなければなりません。


以下も見よ





データのモデル化

















トップへ












トップへ












トップへ












トップへ
目次
Xcode 11 の新機能

  • 概要
  • 以下も見よ












  • トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ