Hashable 을 사용하는 이유가 뭘까

DiffableDataSource, MVVM, RxSwift 를 사용하는 간단한 프로젝트를 구현해보는 과정에서 데이터 모델링을 구현하고 있었는데 Hashable 프로토콜을 선언해 모델을 구현하는 점이 확인되어 이 개념을 알고 넘어가려고 한다.

struct Section: Hashable {
    let id: String
}

enum Item: Hashable {
    case banner(HomeItem)
    case normalCarousel(HomeItem)
    case listCarousel(HomeItem)
}

struct HomeItem: Hashable {
    let title: String
    let subTitle: String?
    let imageUrl: String
}

이렇게 데이터 모델링을 구현할때 Hashable의 프로토콜을 가져와 구현하는 것을 볼 수 있었는데 왜 이렇게 사용하는 걸까

먼저 해시에 대한 개념을 먼저 잡고 가자

 

해쉬란 (Hash)

데이터를 특정 규칙에 따라 간단한 숫자로 만든 것을 해쉬 값이라고 불린다.

원본 데이터를 해쉬함수를 사용해 64bit 값으로 변환하는 것이다.

 

같은 데이터를 가지고 있다면 각 데이터의 해쉬값도 동일하다!

import Foundation

let greeting1: String = "Hello, World!"
let greeting2: String = "Hello, World!"
let differentGreeting: String = "Goodbye, World!"

print("greeting1 hashValue: \\(greeting1.hashValue)")     // 예: 1234567890123456789 - 해시 값
print("greeting2 hashValue: \\(greeting2.hashValue)")     // 예: 1234567890123456789 - greeting1과 동일한 해시 값
print("differentGreeting hashValue: \\(differentGreeting.hashValue)") // 예: 9876543210987654321 - 다른 문자열의 해시 값

// 만약 다른 문자열을 추가로 비교하고 싶다면, 아래와 같이 추가할 수 있습니다.
let studySwift: String = "Let's study Swift together"

print("studySwift hashValue: \\(studySwift.hashValue)") // 예: 9876543210123456789 - 다른 해시 값

예제와 같이 동일한 데이터를가지고 있는 다른 변수의 경우에도 결국엔 해쉬값은 동일한 점을 볼 수 있다.

이를 통해 동일한 데이터는 동일한 해쉬값을 가지는 것을 볼 수 있다.

이러한 해쉬값은 코드가 실행될 때 마다 변경된다는 것을 인지 해야한다!

 

위에 있는 내용을 토대로 반대로 생각해보자면

해쉬값이 동일한 2개의 값이 같은 데이터가 아닐 수 있다는 것이다!

 

Hashable을 사용하는 이유는 뭘까

그럼 다시 Hashable 프로토콜을 살펴보자 이 Hashable은 객체를 해시 테이블에서 효율적으로 사용할 수 있게 해준다.

 

해시 값을 사용하므로써 객체를 고유하게 식별할 수 있는 정수값을 알게 되고 그로인해 여러 방면에서 활용도가 높아진다!

struct Person: Hashable {
    var id: Int
    var name: String

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.id == rhs.id && lhs.name == rhs.name
    }
}

위 예제에서 Person 구조체는 hashable 프로토콜을 사용하고 id 속성을 이용해 해시 값을 생성한다.

이를 통해 == 연산자를 오버라이드하여 두 객체가 동일한지 비교가 가능해진다.

 

이제 Hashable을 사용해야하는 이유에 대해서 알아보자

 

딕셔너리 키로 사용

var peopleDict: [Person: String] = [Person(id: 1, name: "Alice"): "Engineer",
                                    Person(id: 2, name: "Bob"): "Designer"]

Hashable를 구현하면 객체를 딕셔너리의 키로 사용할 수 있게 된다.

딕셔너리는 내부적으로 해시 테이블을 사용하여 키-값을 저장하기 때문에 Hashable를 사용해 딕셔너리와 같이 키를 사용할 수 있게 된다.

 

집합(Set)의 요소로 사용하기

Hashable를 구현하면 객체를 집합의 요소로 사용할 수 있게 된다. 집합은 고유한 값만 저장하며, 중복된 값을 허용하지 않는다.

집합도 내부적으로 해시 테이블을 사용하여 요소를 관라하므로 Hashable 프로토콜을 준수해야한다.

var peopleSet: Set<Person> = [Person(id: 1, name: "Alice"), Person(id: 2, name: "Bob")]

데이터의 고유성 보장

Hashable를 사용하면 데이터의 고유성을 쉽게 보장할 수 있다. 예를 들어 집합은 동일한 해시 값을 가진 두 객체가 존재할 수 없게하기 때문에 중복된 데이터를 방지할 수 있다.

검색 속도가 빠르다

원래는 데이터를 가져올 때 순차적으로 다 찾아서 맞는 데이터를 가져온다면, Hashable를 사용하게 되므로써 해쉬값을 index로 사용해 원하는 값의 위치를 한번에 알 수 있다!

시간 복잡도로 본다면 원래는 O(n)이라면 O(1)로 더욱 빠르게 검색이 가능하다는 것을 알 수 있다.

 

  • Hashable를 채택하는 이유는 class, enum, struct 를 딕셔너리 형태의 key로 사용하기 위해서 사용한다고 생각하면 된다!