文書   >   Swift   >   共通プロトコルの採用
記事
共通プロトコルの採用
Swift プロトコルに準拠していることを確認して、カスタム型を使いやすくします。
概要
プログラムでデータをモデル化するためにカスタム型を使用する場合、2 つの値が同じか異なるか、または特定の値が値のリストに含まれているかどうかを頻繁に確認する必要があります。この機能、および値を Set に格納したり、辞書内のキーとして使用したりする機能は、2 つの関連する標準ライブラリプロトコル Equatable と Hashable によって管理されています。
- 等号 (==) および不等号 (!=) 演算子を使用して、equatable 型のインスタンスを比較できます。
- hashable 型のインスタンスは、その値を数学的に単一の整数に減らすことができ、これは、検索 (lookup) を一貫して高速にするために、Set および辞書によって内部的に使用されます。
多くの標準ライブラリ型は、文字列、整数、浮動小数点値、ブール値、および 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 にすることができます。型を宣言するときに Equatable と Hashable を採用プロトコルのリストに追加すると、コンパイラは自動的に 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 に準拠しなければなりません。
- 列挙型の場合、全ての 関連値は Equatable および Hashable に準拠していなければなりません。(関連値のない列挙型は、採用を宣言しなくても Equatable および Hashable に準拠しています。)
Equatable と Hashable に手動で準拠
以下の場合には、型に対して Equatable と Hashable への準拠を手動で実装する必要があります。
- 型が以前のセクションにリストされている基準を満たしていない。
- 型への準拠をカスタマイズしたい。
- 他のファイルまたはモジュールで宣言された型を準拠するように拡張したい。
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 } }
Player を Hashable プロトコルに準拠させるには、別の拡張機能で準拠を宣言し、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 型は両方のメソッドで name と position を使用しています。
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 つをオーバーライドする場合は、その保証を維持するために他の宣言もオーバーライドしなければなりません。
以下も見よ
データのモデル化
データとモデルの動作を保存する方法を決定します。
トップへ
トップへ
トップへ
トップへ