[Swift] Hashable
안녕하세요. 오늘은 제너릭을 공부할 때도 딕셔너리를 공부할 때도 언급되는 Hashable
프로토콜에 대해 공부해보려 합니다. 사실은 제너릭에 관한 내용을 정리하려 했으나 그 이전에 앞서 Hashable
을 먼저 정리해야 할 것 같아서 이렇게 글을 작성하게 되었습니다.
그럼 바로 시작해보도록 하겠습니다.
Overview
스위프트에서 딕셔너리의 키와 세트로 사용되기 위해서는 해당 타입은 반드시 Hashable
프로토콜을 준수해야 합니다. 그렇기 때문에 String, Integer, Floating-point 그리고 Boolean 타입과 같은 Standard library에 속한 많은 타입들은 Hashable
프로토콜을 준수합니다. 심지어는 세트 타입도 기본적으로는 해시값을 제공합니다.
또한 열거형 역시 연관 타입을 지정하지 않으면 기본적으로 Hashable
프로토콜을 자동으로 준수하고 여러분이 만든 타입도 Hashable
프로토콜을 준수한다면 딕셔너리의 키나 세트로 사용이 가능합니다.
Hashable
프로토콜을 준수하는 모든 타입의 인스턴스는 hashValue
라는 정수형 프로퍼티를 갖고 있으며 이 값은 각각의 인스턴스를 식별하는 값이 됩니다. 그렇기 때문에 반드시 하나만 존재해야 하는 딕셔너리의 키값이나 중복된 값은 허용하지 않는 자료구조인 세트에 들어가는 값들은 Hashable
해야 한다는 것입니다.
- String은 기본적으로 Hashable 프로토콜을 준수하기 때문에 딕셔너리의 키값으로 사용될 수 있다.
- 사용자 지정 타입이 Hashable 프로토콜을 준수하였기 때문에 그리고 해당 타입의 프로퍼티의 타입도 Int로 Hashable 프로토콜을 준수하였기 때문에 이는 딕셔너리의 키값으로 사용될 수 있습니다.
만일 같은 타입의 두 인스턴스가 같다면 두 인스턴스의 해시 값 역시 같을 것입니다. 하지만 그 역은 성립하지 않습니다. 즉 두 인스턴스의 해시 값이 같다고 두 인스턴스는 같은 것이 아닙니다.
Important
해시 값은 프로그램의 매 실행마다 같은 값을 보장하지 않습니다. 그러므로 추후에 사용을 위해 해시 값을 저장하는 행위는 하지 말아야 합니다.
Comforming to the Hashable Protocol
사용자 정의 타입을 딕셔너리의 키나 세트에서 사용하기 위해서는 Hashable
프로토콜을 준수해야 한다고 했습니다. Hashable
프로토콜은 Equatable
프로토콜을 상속받습니다. 그렇기 때문에 이 두 프로토콜의 요구 사항을 모두 충족시켜야 합니다.
사용자 정의 타입은 선언부에 Hashable
을 작성해주어 해당 프로토콜을 준수한다 명시하고 만일 해당 타입이 구조체라면 저장 프로퍼티의 타입들이 모두 Hashable
프로토콜을 준수해야 하고 열거형이라면 연관 값의 타입이 Hashable
프로토콜을 준수해야 합니다.
스위프트 4.1 이전에는 Hashable
프로토콜을 준수하기 위해서는 hashValue
값과 ==
연산자를 다음과 같이 직접 구현해왔습니다.
하지만 스위프트 4.1부터는 구조체와 열거형에 대해서는 컴파일러가 이를 자동으로 구현해줍니다. 물론 이를 사용자가 직접 정의할 수도 있습니다.
하지만 클래스에 대해서는 컴파일러가 자동으로 구현해주지 않기 때문에 사용자가 위의 코드처럼 직접 구현해주어야 합니다. 그리고 hashValue
의 로직을 작성할 때는 사용자 정의 타입을 이루고 있는 데이터들에 따라 적절한 해시 알고리즘을 사용해야 합니다. 세트와 딕셔너리의 성능은 얼마나 이들이 충돌을 최소화할 수 있는지에 달려있기 때문입니다.
마무리
오늘은 이렇게 Hashable
프로토콜에 대해 간단히 알아보았습니다. Hashable
프로토콜이 언급될 때마다 그저 대략적인 감으로 이해하고 넘어갔었는데 이번 기회를 통해 보다 명확하게 개념이 잡힌 것 같아서 유익한 시간이었습니다. 감사합니다.
참고자료
- Hashable
- Swift Hashable
- How to get Equatable and Hashable for free