Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
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 31
Archives
Today
Total
관리 메뉴

sanichdaniel의 iOS 개발 블로그

Hashable 본문

swift

Hashable

sanich8355 2020. 10. 7. 01:12

요약

Set의 원소 Dictionary의 키값으로 내가 만든 타입을 사용하려는 경우 Hashable을 채택을 해야 된다.
특정 조건만 만족이 된다면, Hashable 채택만으로 기본 구현이 제공된다.
이때 조심해야하는건, Set, Dictionary의 contains 함수는 hashValue를 비교하여 존재여부를 파악한다.
swift 현재 버전에서는 hash(into:) 함수로 hashValue가 생성되는데, hash(into:) 함수에서 Equatable 구현에서 비교에 사용되는 프로퍼티에 맞추어 combine(:)을 호출해주지 않으면 contains(:) 함수가 비정상적으로 동작할수 있다. 

애플 공식 문서의 정의는

A type that can be hashed into a Hasher to produce an integer hash value.

정수 hash값을 제공하는 타입이다.

같은 타입의 인스턴스인 경우 a == b 이면 a.hashValue == b.hashValue이다. 

역은 성립하지 않을수 있다. (다른 키가 같은 해시값 = 해시 충돌)

swift 기본 data type은 기본으로 Hashable하고, optional, array의 기본값이 Hashable 한경우 Hashable하다.

hashValue는 매번 달라지니 저장하면 안된다

 

hash값은 Set에서 Element로, Dictionary에서 Key로 이용된다

Dictonary 자체가 hash table이다. 

// Set 구현
@frozen public struct Set<Element> where Element : Hashable { }

// Dictonary 구현
@frozen public struct Dictionary<Key, Value> where Key : Hashable { }

 

Hashable을 채택하기

아래의 경우 Hashable 프로토콜을 채택하기만 하면 컴파일러가 프로토콜 요구조건을 생성해준다.

  • struct이면 모든 저장 프로퍼티가 Hashable을 채택해야한다.

  • enum인 경우 모든 associated value가 Hashable을 채택해야 한다. (associated value가 없는 enum은 자동으로 Hashable 하다)

위의 케이스가 아닌 경우에는 hash(into:) 함수를 직접 구현해줘야한다.

hash(into:) 함수안에서 Equatable 구현에서 비교에 쓰이는 프로퍼티들에 대해 combine(:) 함수를 호출해주면된다.

 

Equatable 구현에서 비교에 쓰이는 프로퍼티들에 대해 combine(:) 함수를 호출 안한다면 어떤일이 일어나는지 아래 예시에서 확인해보자

 

예시 1)

Newstory의 Set을 만들고 싶다. id로 인스턴스가 같은지 다른지를 구별하고 싶다.

아래의 story1과 story3는 id가 같으나, title이 다르다는 이유로 다른 객체로 취급 받는다.

struct NewsStory: Hashable {
    var id: Int
    var title: String
}

let story1 = NewsStory(id: 1, title: "What's new in Swift 5?")
let story2 = NewsStory(id: 2, title: "What's new in Swift 6?")

var stories = Set<NewsStory>()
stories.insert(story1)
stories.insert(story2)
print(stories)

let story3 = NewsStory(id: 1, title: "What's new in Swift 7?")
stories.insert(story3)
print(stories) // 3 원치 않은 결과

id가 같으면 같다고 Equatable 프로토콜만 구현해주면 id가 같은 경우 중복으로 처리되어 insert가 실패하도록 할 수 있다.

이때까진 hash(into:) 함수가 따로 필요없어 보였었다... 

struct NewsStory: Hashable {
	...
    
    static func ==(lhs: NewsStory, rhs: NewsStory) -> Bool {
        return lhs.id == rhs.id
    }
}

contains 메서드를 실행해보자

contains 메서드가 어떨때는 true를 리턴하고 어떨때는 false를 리턴한다

(스택 오버플로우에도 있는 질문 stackoverflow.com/questions/58835370/swift-set-equatable-sometimes-true-sometimes-false)

let story1 = NewsStory(id: 1, title: "What's new in Swift 5?")
let story2 = NewsStory(id: 2, title: "What's new in Swift 6")

var stories = Set<NewsStory>()
stories.insert(story1)
stories.insert(story2)

let story3 = NewsStory(id: 1, title: "What's new in Swift 7")
print(stories.contains(story3)) // true false 번갈아가며 리턴한다!!!

contains() 함수는 hash값을 이용하여 원소가 존재하는지 확인한다. 

따라서 id 기준으로 hash(into)를 구현해야 오류가 발생안한다

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

...
print(story1.hashValue == story4.hashValue) // true

 

Reference type Hashable

출처

developer.apple.com/documentation/swift/hashable

 

zeddios.tistory.com/498

 

www.hackingwithswift.com/example-code/language/how-to-conform-to-the-hashable-protocol

 

www.hackingwithswift.com/articles/199/avoiding-near-duplicates-in-sets

 

forums.swift.org/t/why-does-hashable-require-equatable/16817/3

 

bugs.swift.org/browse/SR-3330?focusedCommentId=19911&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-19911

'swift' 카테고리의 다른 글

일급 시민 함수 - swift  (0) 2020.10.14
Identifiable  (0) 2020.10.07
KeyPath  (0) 2020.10.03
Generic where cause  (0) 2020.10.01
Opaque Types  (0) 2020.09.29