본문 바로가기

iOS/Swift

Swift) Hashable에 대해 알아보자

안녕하세요 :) 소들입니다

일주일에 글 한 개 쓰기 위해!!!!!!!!! 노력하는 주인장!!!!!!

아직 일요일 안지났으니까 일주일 안 지난 거겠죠? :(

내 롤 절대지켜..;;

 

라고 써놓고 일이 너무 바빠서

이제야 올립니다 휴; 대신 이번 주에 두개 쓰겠음

 

쨌든 오늘은 Hashable이란 프로토콜에 대해서 알아볼 거예요!

어떻게 사용하는지 함 봐봅시다!!!

모든 포스팅은 편의 말투로 합니다~!

 

 

 

 

1. 해시(Hash)가 무엇일까?

 

 

혹시 해시 테이블이 뭔지 모르실 분은 이 포스팅을 먼저 보시길 추천드립니다z

안 볼 사람을 위해 몇 줄 글을 긁어와보면

자, 우리가 아무렇지 않게 사용하던 딕셔너리를 생각해보셈!!

 

딕셔너리는 Key - Value로 값을 저장하기 때문에,

특정 Key를 던져주면, 그 키에 해당하는 Value가 나오는 형태잖음?

 

딕셔너리가 바로 해시 테이블이란 것으로 구현되어 있기 때문에,

해시 테이블도 당연히 Key - Value로 값을 저장

 

아니 그래서 해시 테이블이 뭐셈!

해시 테이블은 내부적으론 배열로 구현이 되어 있음!!

 

무슨 말이냐면,  우리가 특정 Key - Value를 저장한다고 하면

해당 Key를 해시함수란 것을 통해 해시를 하고,

결과 값인 해시 주소 값에 해당하는 해시 테이블 슬롯에 Value를 저장하는 것임!

 

 

 

 

이렇게!!!!

 

Key 값을 해싱 함수에 넣어 해시하여 배열의 주소값(해시 주소값)을 얻고,

그 주소값에 맞는 index에 Value값을 저장하는 것임

 

아 오키오키 여기까지 이해 했음

만약 이해 못했다면 위에서 링크한 해시 테이블 포스팅에서 매우 자세히 나와있으니 읽어보고 오셈 

 

자 그럼 딕셔너리를 사용할 때 다음과 같이 Key 값으로 기본 자료형(Int, String 등)을 쓸 경우

 

 

let myDict: [StringInt= ["sodeul"28]

 

 

아무런 문제 없이 사용할 수 있음

왜!? Equatable과 마찬가지로 기본 자료형은 컴파일러에 의해 Hashable 프로토콜이 알아서 채택 되거든!

 

근디 만약 우리가 다음과 같이 구조체로 타입을 만든 경우,

 

 

struct Human {
    let name: String
    let age: Int
}
 
let myDict: [HumanInt    // Type 'Human' does not conform to protocol 'Hashable'
 

 

 

이때는 Human안에 name, age란 프로퍼티가 기본 자료형이지만,

 어떤 값으로 해시를 해야할지 모르기 때문에 에러가 남

 

그 에러가 바로 "Hashable"이란 프로토콜을 준수하지 않았다!!" 라는 에러임

 

따라서 Dictionary에서 Key값은 무조건 어떤 프로퍼티를 해시 할지에 대해 정의해야하는,

바로 Hashable이란 프로토콜을 준수하고 있는 타입만이 가능한 것임

(딕셔너리 뿐 아니라 해시 테이블을 쓰는 Set을 쓸 때도 마찬가지!)

 

참고로 Hashable이란 프로토콜은 다음과 같이 생겼음

 

 

 

 

hashValue는 더이상 사용하지 않는 프로퍼티라고 하고,

필요할 경우 hash(into:) 함수를 직접 구현해주어야 함

 

아, 그리고 또 Hashable은 자체적으로 바로 전 포스팅에서 배운

Equatable이란 프로토콜을 준수하고 있음!!

 

음.. 내생각에 Equatable을 준수해야 하는 이유는

해시 테이블 충돌 시 Chaining 기법의 경우

같은 index에 Key-Value 값을 연결 리스트를 이용해서 저장하기 때문에

나중에 Value를 꺼낼 때 같은 index에 저장된 연결 리스트 노드들 중에

고유한 값인 key를 비교(==)하며 맞는 Value를 꺼내기 위해서라고 생각하고 있었는데...

확실한 정보는 아니고,, 내 뇌의 생각이라 확실한 답을 알게되면 추가 하겠음 ....ㅎ

 

쨌든 이러한 이유로 Hashable을 채택 하려면,

Equatable을 채택하는 것만으로 구현이 되지 않는 클래스의 경우 Equatable 또한 같이 구현해줘야 함

 

자 그럼 이제 구조체, 클래스, 열거형에서

Hashable이란 프로토콜을 채택하는 방법에 대해 자세히 알아보겠음 :)

 

 

 

 

2. 구조체 / 클래스/ 열거형에서 Hashable을 채택하는 방법

 

 

2-1. 구조체

 

 

구조체의 경우 매우 간단함

Equatable과 마찬가지로 만약 구조체 내 프로퍼티가 모두 기본 자료형이라면,

 Hashable을 채택하는 것만으로 추가 구현 없이 사용 가능

 

 

struct HumanHashable {
    let name: String
    let age: Int
}
 
let myDict: [HumanInt= [:]      

 

 

쨔쟌 이렇게 Hashable을 채택해주는 것 만으로

이제 Human이란 구조체를  Dictionary의 Key로 사용 가능함!!!

 

 

 

2-2. 클래스

 

Equatable과 마찬가지로 클래스는 채택하는 것만으로 되지 않고

직접 함수를 구현 해야할 거 같은 감이 오지 않음? ^v^..;;;

 

맞음 클래스는 Hashable이란 프로토콜을 준수하는 것도 모자라

직접 hash함수를 구현해주어야 함

 

hash 함수 구현 어떻게 해!!! 해줘!!!! 엉엉!!!! 하는 나같은 개복치 개발자를 위해

hasher이란 파라미터의 combine이란 메서드를 이용하면 어렵지 않게 구현할 필요가 없음

 

 

class Human {
    let name = "Sodeul"
    let age = 28
}
 
extension HumanHashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(age)
    }
}
 
let myDict: [HumanInt= [:]

 

 

이렇게! hash 메서드를 직접 구현해주는데,

이때 hasher.combine이란 메서드에 어떤 파라미터를 해시할 것인지를 넣어주는 것임

 

자 주의사항을 봅시다

 

특별한 경우가 아니면 combine에는 해당 타입의 모든 저장 프로퍼티를 전달하는 것임

또한, combine 파라미터로 전달하는 프로퍼티는 반드시 Hashable을 준수하고 있어야 함

(위에서 name, age는 기본 자료형이고, 자동으로 Hashable을 준수하고 있기 때문에

위처럼 combine의 파라미터로 전달 할 수 있단 말!)

 

 

이렇게 하면 끝날 것 같지만

 

 

 

 

에러떠용

아까 말했잖음 Hashable은 Equatable을 채택하고 있기 때문에,

자동으로 제공되지 않는 클래스의 경우 직접 구현 하셔야 함 낄낄

 

 

class Human {
    let name = "Sodeul"
    let age = 28
}
 
extension HumanHashable {
    static func == (lhs: Human, rhs: Human) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(age)
    }
}
 
let myDict: [HumanInt= [:]

 

 

이렇게!! 그럼 이제 Human이란 클래스를 

Hashable 프로토콜을  채택해야 하는 자료형에서 사용할 수 있음

 

정리하자면, 클래스에서 Hashable을 사용하고 싶다면,

hash 메서드를 직접 구현 + == 메서드 직접 구현

라고 할 수 있겠음다

 

 

 

2-3. 열거형

 

 

1️⃣ 연관값이 없는 열거형

 

 

연관값이 없는 열거형의 경우,

 

 

enum Gender {
    case male
}
 
let myDict: [GenderInt= [:]

 

 

Hashable을 채택하지도 않아도 자동으로 구현이 됨

 

 

 

2️⃣ 연관값이 있는 열거형

 

 

연관값이 있는 열거형의 경우,

 

 

enum Gender: Hashable {
    case male(age: Int)
}
 
let myDict: [GenderInt= [:]

 

 

Hashable을 채택한다고 선언해주고,

연관값의 타입(Int)이 모두 Hashable을 채택(구현)하고 있어야 함

 

 

 

 

 

 

 

.

.

.

자 오늘은 Hashable이란 프로토콜에 대해 알아봤어요 :)

이번주면 바쁜일이 끝날 것 같아서

다음 주엔 꼭 기한 지켜서 글을 올리도록 해보겠슴니다 하하

 

읽어주셔서 감사!

오타 및 잘못된 내용 피드백 대환영

 

 



Calendar
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
최근 댓글
Visits
Today
Yesterday