Written by
doremin
on
on
Swift의 Hashable
Swift의 Hashable
1. Hashable 이란?
Swift에서 Hashable은 값을 해시(hash)할 수 있도록 보장하는 프로토콜입니다.
Set이나 Dictionary 같은 컬렉션에서 요소나 키를 빠르게 검색하기 위해 내부적으로 해시 테이블을 사용하는데, 여기서 필요한 것이 바로 Hashable입니다.
String,Int,Bool같은 기본 타입들은 기본적으로Hashable을 채택하고 있습니다.- 우리가 정의한
struct나enum도 저장 프로퍼티가 전부Hashable이면 자동으로 합성됩니다. - 해싱이란, 값의 “핵심 구성 요소”를 입력으로 넣어 정수 해시값을 계산하는 과정입니다.
struct User: Hashable {
let id: Int
let name: String
}
위의 User는 자동으로 Hashable을 따릅니다. (id와 name 모두 Hashable이므로)
2. Hashable의 조건
Hashable은 Equatable을 상속하므로, 두 가지 규칙이 반드시 지켜져야 합니다.
- a == b이면 반드시 a.hashValue == b.hashValue여야 한다.
- 같다고 판정되는 값은 항상 같은 해시를 가져야 합니다.
- 반대로, a.hashValue == b.hashValue라고 해서 반드시 a == b일 필요는 없다.
- 해시 충돌이 허용되기 때문입니다. 다른 값이 같은 해시를 가질 수도 있습니다.
이 원칙을 어기면 컬렉션의 동작이 꼬이게 됩니다.
그런데 공식 문서에서는
3. ==이 true인데 hashValue가 다르면 생기는 일
가장 위험한 경우는 동등한 값인데 서로 다른 해시값을 가지는 경우입니다. 이건 Hashable의 규칙을 어긴 구현으로, Set이나 Dictionary 같은 컬렉션에서 심각한 문제를 일으킵니다.
예시를 보겠습니다.
struct Foo: Hashable {
let id: Int
let name: String
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func ==(lhs: Foo, rhs: Foo) -> Bool {
lhs.name == rhs.name // id는 무시
}
}
let a = Foo(id: 1, name: "A")
let b = Foo(id: 2, name: "A")
print(a == b) // true
print(a.hashValue == b.hashValue) // false
let set: Set = [a, b]
print(set.count) // 2 (둘은 같은 값인데도 다른 원소로 취급됨)
- ==에 따르면 a와 b는 같아야 하지만,
- hashValue는 다르기 때문에 Set에서는 서로 다른 원소로 들어가 버립니다.