본문 바로가기
iOS/Swift

[Swift] Hashable

by Jiseong 2022. 5. 2.

 와 너무 졸리다

 

오늘은 Hashable.. 그저 딕셔너리의 키, 세트같은 중복값이 들어갈 수 없는 애들은 Hashable하다 라고만 알고있었는데, 저번주에 combine인가 hash 인가하는 메서드를 보고 공부해봐야겠다고 느껴서..

 

먼저 Hashable이란 정수 해시값을 제공하여 유일하게 표현이 가능한 방법을 제공하는 프로토콜이다.

 

보통 hash한 값이다.. 하면 뭐.. 유일한 값, 중복되지 않는 값 -> UUID...? 뿐이 생각안나네

 

ㅋㅋㅋ오 때려맞춤 ㅋㅋㅋㅋ

 

요건 UUID에 대한 잼난 글이 있길래.. 

 

https://medium.com/@jang.wangsu/ios-swift-uuid는-어떤-원리로-만들어지는-것일까-22ec9ff4e792

 

[iOS, Swift] UUID는 어떤 원리로 만들어지는 것일까..

갑자기! 문득! 그냥! 별생각 없이!!! iOS? UUID의 생성 알고리즘에 대해 고민이 들어서 검색해 봤어요.

medium.com

대단하십니다 클린트 장 님..

 

 

공식문서의 꼬부랑글씨를 보면.. 핵심적인 문장부터 좀 보자

 

먼저 첫번째, Swift Standard Library의 타입(String, Int, Floating-point, Bool, Set)은 기본적으로 적용이 되어있단다.

 

두번째로 중요해보이는 건.. 모든 프로퍼티가 Hashable을 채택하고 있는 구조체와 연관값 없이 열거형을 정의할 경우 기본적으로 hash 메서드를 컴파일러가 적용시킨단다. 아 물론 채택해야 적용시키는거임;; (연관값 없는 열거형 제외)

 

세번째론.. 기본적으로 적용되지 않는 사용자 정의 타입, 구조체 내부에 뭐 클래스가 있을 수도 있는 거고 열거형에 연관값이 있을 수도 있는거고.. 그럴 땐 hash(into:) 메서드를 직접 구현하여 사용해야한다.

 

그러믄 뭐... 직접 해보자

 

구조체부터!!

struct 아니렉걸려 {
    let 렉: String
    let 좀: Int
}

let 꺼져주세요: [아니렉걸려: Int]

딕셔너리의 키값은 Hashable 해야하기에 에러가 나는 모오습

 

Hashable 채택해주면..

struct 아니렉걸려: Hashable {
    let 렉: String
    let 좀: Int
}

let 꺼져주세요: [아니렉걸려: Int]

채택만 해주고 hash(into:) 메서드를 따로 구현해주지 않아도 구조체 내부 프로퍼티의 타입이 모두 hashable을 채택하고 있는 기본 타입이므로 에러가 나지않고 적용된 모오습

 

간단함

 

열거형!!!!!

enum 아니왜이러세요 {
    case 타자가
    case 밀리ㅇㅇ요
}

let 쩨발좀: [아니왜이러세요: Int] = [:]

 

에러 안나고 잘 됨

 

연관값이 없는 열거형은 별도의 Hashable 채택없이도 가능

 

그 증거는 요기

When you define an enumeration without associated values, it gains Hashable conformance automatically

 

하지만 연관값이 있는 경우엔

enum 아니왜이러세요 {
    case 타자가(타자: String)
    case 밀리ㅇㅇ요(욕이나올듯해요: String)
}

let 쩨발좀: [아니왜이러세요: Int] = [:]

Hashable을 채택을 해주어야한다.

 

물론 hash(into:) 메서드는 따로 정의해줄 것이 없거나, 연관값의 타입이 Hashable을 채택한 타입이라면 자동으로 적용이 된다.

enum 아니왜이러세요: Hashable {
    case 타자가(타자: String)
    case 밀리ㅇㅇ요(욕이나올듯해요: String)
}

let 쩨발좀: [아니왜이러세요: Int] = [:]

 

클래스는..?

이게 아까 처음에 말했던 hash(into:), combine 메서드를 사용하는 대표적인 예이다.

일단 이건 Hashable을 채택해주지 않았고, 자동 구현, 채택마저 되지 않는 상황이기때문에 에러가 나는 것은 당연한 것이다.

 

 하지만 Hashable을 채택해줘도 구조체와 열거형과 달리 hash(into:) 메서드를 자동으로 제공하지 않기 때문에 사용자가 스스로 구현을 해주어야 한다.

 

class 렉가라고: Hashable {
    let 오ㅋ: String = "오"
    let 됐다ㅋ: Int = 1
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(오ㅋ)
        hasher.combine(됐다ㅋ)
    }
}

let 굿: [렉가라고: Int] = [:]

이 때 내부에서 combine이라는 메서드를 사용하는데.. 해당 메서드의 정의를 살펴보자

?

 

어.. 일단은 입력된 값과 해시값을 혼합해주는 메서드란다.

 

그냥.. 유일한 값이다 라는 걸 붙여준다 정도로 이해했다..

 

그리고 보면 mutating 메서드임을 알 수 있는데, 이는 combine 메서드가 Hasher 라는 구조체의 메서드이고 구조체의 프로퍼티 값을 변경하므로 mutating인 것 같다.

 

Hasher는 해시 값을 만드는 해시 함수가 있는 struct

 

여까지 오면 이제 Hashable에 대한 에러는 사라졌을 것이다.

 

하지만..

Equatable 프로토콜에 대한 에러가 발생한다.

 

이 에러가 발생하는 이유는 Hashable은 Equatable을 상속받고 있기 때문이다.

왜 Hashable은 Equatable을 상속 받을까?

Set, Dictionary의 key 등의 유일한 값으로 사용하기 위해서는 Hashable을 준수하고 있어야 한다.


Set, Dictionary의 Key는 중복을 허용하지 않고 Hashable 또한 마찬가지이다.


그러므로 Key가 중복인지 아닌지를 판별을 해야하는데, 이 때 Equatable을 사용한다.

 

Hashable이 Equatable을 상속받고 있는 이유는 hash 메서드로 해시값을 생성하였을 때 해시값이 동일한 문제가 발생할 수 있는데, 동일한 해시값에 대해 같은 값이 할당되어있는 경우를 해시 충돌이라고 한다.

 

이 때 값을 직접 비교하여 찾기 위해 == 비교 연산이 필요하다.

 

이것까지 정의를 해보자

 

class 렉가라고 {
    let 오ㅋ: String = "오"
    let 됐다ㅋ: Int = 1
}

extension 렉가라고: Hashable {
    static func == (lhs: 렉가라고, rhs: 렉가라고) -> Bool {
        return lhs.오ㅋ == rhs.오ㅋ && lhs.됐다ㅋ == rhs.됐다ㅋ
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(오ㅋ)
        hasher.combine(됐다ㅋ)
    }
}

let 굿: [렉가라고: Int] = [:]

 

이럼 끝이다.

 

기본적으로 hashable, hash 메서드를 자동으로 제공하지 않는 사용자 정의 타입의 경우엔 이렇게 hash메서드와 연산자를 직접 구현해줘 사용할 수 있다.

 

끝으로.. 

 

Set, Dictionary key가 되려면 왜 Hashable 해야하는가?

 

swift의 set과 dictionary key에는 순서가 없기때문에 해당 컬렉션의 요소를 빠르게 찾기 위해 유일한 값(해시값)을 지정하여 성능을 높일 수 있다.

'iOS > Swift' 카테고리의 다른 글

[Swift] defer  (0) 2022.06.10
[Swift] mutating  (2) 2022.05.08
[Swift] Increasing Performance by Reducing Dynamic Dispatch  (0) 2022.04.22
Swift) COW(Copy On Write)  (0) 2022.02.20
Swift) self vs Self  (0) 2022.02.20

댓글