본문으로 건너뛰기

Adopting Common Protocols

공통 프로토콜 채택

Make your custom types easier to use by ensuring that they conform to Swift protocols.

스위프트 프로토콜을 준수하도록해서 사용하기 쉽게 커스텀 타입을 만들자.


Overview

개요

When using custom types to model data in your programs, you may frequently need to check whether two values are the same or different, or whether a particular value is included in a list of values. This capability, as well as the ability to store values in a set or use them as keys in a dictionary, are governed by two related standard library protocols, [Equatable](https://developer.apple.com/documentation/swift/equatable) and [Hashable](https://developer.apple.com/documentation/swift/hashable).

커스텀 타입을 사용하여 프로그램의 데이터 모델링을 할 때, 자주 두 값이 같은지 다른지 또는 특정 값이 값 목록에 포함되어 있는지 여부를 확인해야할 수 있다. 이 기능은 값을 세트(set)에 저장하거나, 딕셔너리의 키로 사용되는 함께 Equatable과 Hashable이라는 두 가지 관련 표준 라이브러리 프로토콜에 의해 관리된다.

  • You can compare instances of an equatable type by using the equal-to (==) and not-equal-to (!=) operators.
  • An instance of a hashable type can reduce its value mathematically to a single integer, which is used internally by sets and dictionaries to make lookups consistently fast.
  • equal-to(==)와 not-equal-to(≠) 연산자를 사용하여 equatable 타입의 인스턴스를 비교할 수 있다.
  • hashable 타입의 인스턴스는 일관적으로 빠르게 조회할 수 있도록 세트와 딕셔너리에 내부적으로 사용되는 단일 정수로 수학적으로 값을 줄일 수 있다.

Many standard library types are both equatable and hashable, including strings, integers, floating-point values, Boolean values, and collections of equatable and hashable types. The == comparison and the contains(_:) method call in the following example depend on strings and integers being equatable:

문자열, 정수, 부동 소수점, 부울 값 그리고 비교가능 및 해시가능한 콜렉션 타입을 포함한 많은 표준 라이브러리 타입은 또한 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!")
}

Conforming to the Equatable and Hashable protocols is straightforward and makes it easier to use your own types in Swift. It’s a good idea for all your custom model types to conform.

Hashable과 Equatable 프로토콜을 따르게하는 것은 간단하고 스위프트에서 자신의 타입을 쉽게 사용할 수 있게 만든다. 모든 커스텀 모델 타입이 준수하는 것이 좋다.

Conform Automatically to Equatable and Hashable

자동으로 적용되는 Eqatable 및 Hashable

You can make many custom types equatable and hashable by simply declaring these protocol conformances in the same file as the type’s original declaration. Add Equatable and Hashable to the list of adopted protocols when declaring the type, and the compiler automatically fills in the requirements for the two protocols:

타입을 선언한 동일한 파일에서 프로토콜 준수를 간단히 선언함으로 많은 커스텀 타입을 equatable과 hashable하게 만들 수 있다. 타입을 선언할 때 채택된 프로토콜 목록에 equatable과 hashable을 추가하면 컴파일러는 자동으로 파일에 두 프로토콜에 대한 요구사항을 채운다.

/// 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
}
}

With Equatable conformance, you can use the equal-to operator (==) or the not-equal-to operator (!=) with any two instances of the Position type.

Equatable 준수하면 포지션 타입인 어떤 두 인스턴스와 함께 equal-to(==) 연산자나 not-equal-to(≠)연산자를 사용할 수 있다.

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 conformance means that you can store positions in a set and quickly check whether you’ve visited a position before, as shown in the following example:

Hashable 준수는 아래 예시에서 보여지는 것처럼 프로퍼티를 셋 안에 위치를 저장하고 이전에 위치를 방문한 적이 있는지 빠르게 확인할 수 있음을 의미한다.

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)

In addition to simplifying your code, this automatic conformance reduces errors, because any new properties you add to your custom types are automatically included when hashing and testing for equality. A type is eligible for automatic conformance to Equatable and Hashable when it’s a structure or an enumeration that meets these criteria:

추가적으로 코드를 단순화하는 것 이외에도, 이러한 자동적 준수는 커스텀 타입에 추가하는 어떤한 새 프로퍼티가 해싱 및 동등성 테스트 시 자동으로 포함되기 때문에 에러를 줄인다. 타입이 다음 기준을 충족하는 구조체나 열거형일 경우 Equatable 및 Hashable을 자동으로 준수할 수 있다.

  • For a structure, all its stored properties must conform to Equatable and Hashable.
  • For an enumeration, all its associated values must conform to Equatable and Hashable. (Enumerations without associated values have Equatable and Hashable conformance even without declaring adoption.)
  • 구조체에서, 모든 저장된 프로퍼티는 Equatable과 Hashable을 준수해야 한다.
  • 열거형에서, 모든 연관된(associated)값은 Equatable과 Hashable을 준수해야 한다.(연관된 값 없는 열거형는 채택하지 않더라도 Equatable과 Hashable을 준수한다.

기본적으로 구조체 및 열거형을 선언 시 Equatable 및 Hashable이 함께 채택되는 듯하다.


Conform Manually to Equatable and Hashable

수동으로 Equatable 및 Hashable 준수하기

You need to manually implement Equatable and Hashable conformance for a type in these cases:

다음과 같은 경우 타입에 수동으로 Equatable 및 Hashable 준수를 구현할 필요할 수 있다.

  • The type doesn’t meet the criteria listed in the previous section.
  • You want to customize the type’s conformance.
  • You want to extend a type declared in another file or module to conform.
  • 타입이 이전 섹션에 나열된 기준을 충족하지 않는 경우 → 자동으로 적용되지 않는 경우
  • 타입의 준수에 커스터마이징이 필요한 경우
  • 다른 파일이나 모듈에서 준수하기 위해 선언된 타입을 확장하려할 경우
class Player {
var name: String
var position: Position

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

The Player type is a class, so it doesn’t qualify for automatic synthesis of the Equatable or Hashable requirements. To make this class conform to the Equatable protocol, declare conformance in an extension and implement the static == operator method. Compare each significant property for equality in your == method’s implementation:

Player 타입은 클래스이다. 따라서 Equatable이나 Hashable 요구사항의 자동 합성에 적합하지 않다. 이 클래스를 Equtable 프로토콜을 준수하게 만드려면, extension에 준수를 선언하고 static == 연산자 메서드를 구현해야 한다. == 메서드 구현에서 각각의 중요한 프로퍼티가 같은지 비교한다.

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

To make Player conform to the Hashable protocol, declare conformance in another extension and implement the hash(into:) method. In the hash(into:) method, call the combine(_:) method on the provided hasher with each significant property:

Player 클래스가 Hashable 프로토콜을 준수하게 만드려면, extension에 준수를 선언하고 hash(into:) 메서드를 구현해야 한다. hash(into:) 메서드는 각 중요한 프로퍼티와 함께 제공된 해셔에서 combine(_:) 메서드를 호출한다.

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

  • 실습

    // Hashable을 구현하지 않은 경우
    struct Position: Equatable, Hashable {
    var x: Int
    var y: Int

    init(_ x: Int, _ y: Int) {
    self.x = x
    self.y = y
    }
    }

    class Player {
    var name: String
    var position: Position

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

    let APlayer = Player(name: "Kim", position: .init(10, 10))
    let BPlayer = Player(name: "Lee", position: .init(10, 10))

    APlayer == BPlayer // Binary operator '==' cannot be applied to two 'Player' operands


    클래스는 열거형과 구조체가 아니므로, Equatable을 자동준수할 수 없다. 따라서 두 인스턴스 비교가 불가능하다.

    ...

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

    let APlayer = Player(name: "Kim", position: .init(10, 10))
    let BPlayer = Player(name: "Kim", position: .init(10, 10))

    APlayer == BPlayer // false
    APlayer === BPlayer // false

    Player 클래스를 확장한 후 Equtable을 채택하고 == 연산자를 구현하면. APlayer와 BPlayer가 비교 가능하다. Equatable을 수동 구현 시 해당 프로토콜을 준수하도록 == 연산자를 구현되어 있지 않다면 에러가 발생한다.

관련자료


Use All Significant Properties for Equatable and Hashable

Equatable과 Hashable에 대한 중요한 모든 프로퍼티를 사용하라.

When implementing the == method and the hash(into:) method, use all the properties that affect whether two instances of your custom type are considered equal. In the implementations above, the Player type uses name and position in both methods.

== 메서드와 hash(into:) 메서드를 구현할 때, 커스텀 타입의 두 인스턴스가 동일한 것으로 간주되는지 여부에 영향을 미치는 모든 프로퍼티를 사용하라. 위의 구현에서, Player 타입은 두 메서드 모두에서 name과 position을 사용한다.

If your type contains properties that don’t affect whether two instances are considered equal, exclude those properties from comparison in the == method and from hashing in hash(into:). For example, a type might cache an expensive computed value so that it only needs to calculate it once. If you compare two instances of that type, whether or not the computed value has been cached shouldn’t affect their equality, so the cached value should be excluded from comparison and hashing.

두 인스턴스가 동일한 것으로 간주되는지 여부에 영향을 미치지 않는 프로퍼티가 포함되어 있다면 == 메서드와 hash(into:)의 해싱으로부터 해당 프로퍼티를 제외한다. 예를 들어, 타입은 한 번만 계산되면 되도록 비용이 많은 드는 계산 값을 캐시할 수 있다. 두 인스턴스를 비교하는 경우, 계산된 값이 캐시되었는지 여부는 그들이 동일한지에 대해 영향을 미치지 않으므로, 캐시된 값은 비교 및 해싱에서 제외해야 한다.

[Important]

Always use the same properties in both your == and hash(into:) methods. Using different groups of properties in the two methods can lead to unexpected behavior or performance when using your custom type in sets and dictionaries.

항상 hash(into:)와 == 메서드 모두에서 동일한 프로터피를 사용하라. 두 메서드에서 다른 그룹의 프로퍼티를 사용하는 것은 셋과 딕셔너리에서 커스텀 타입을 사용할 때 예기치 않은 동작이나 성능이 발생할 수 있다.



Customize NSObject Subclass Behavior

NSObject 하위 클래스 동작의 커스터마이징

NSObject subclasses inherit conformance to the Equatable and Hashable protocols, with equality based on instance identity. If you need to customize this behavior, override isEqual(_:) the method and hash property instead of the == operator method and hashValue property.

NSObject 하위 클래스는 Equatable과 Hashable 프로토콜 준수를 상속하며, 인스턴스 ID를 기반으로 동등성을 갖는다. 이 동작을 커스텀하고 싶다면, == 연산자 메서드 및 hashValue 프로퍼티 대신에 isEqual(_:) 메서드와 hash 프로퍼티를 재정의(override)하라.

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()
}
}

As noted in the previous section, two instances that are considered equal must have the same hash value. If you override one of these declarations, you must also override the other to maintain that guarantee.

이전 섹션에서 언급한 것처럼, 동일한 것으로 간주되는 두 인스턴스는 동일한 해시 값을 가져야 한다. 이러한 선언 중 하나를 재정의하는 경우, 해당 선언을 유지하려면 다른 선언도 재정의해야한다.