Hash란?
데이터를 관리, 유지하는 자료구조로 데이터들을 해시 함수를 통해 key로 분류하고, 그 key에 따라 value를 저장하는 형태를 뜻한다.
HashTable이란 Key를 이용해 배열의 Index를 찾고, 그 인덱스를 통해 값을 가져온다.
"제리"의 영어 이름을 찾기 위해
1. "제리"라는 key를 hash 함수에 전달한다
2. hash 함수 내에 hash함수를 통해 찾을 value의 인덱스를 찾는다.
3. 찾은 인덱스를 통해 value값 "jarry"를 얻는다.
Hashable이란?
https://developer.apple.com/documentation/swift/hashable
Hashable | Apple Developer Documentation
A type that can be hashed into a to produce an integer hash value.
developer.apple.com
Hashable이란 해시값을 생성하게 하는 Hash가 가능한 타입이다.
기본적으로 swift의 기본 타입들은 모두 Hashable하다.
그러나 개발자가 직접 만든 타입은 Hashable 하지 않기 때문에 Hash를 가능하게 하기 위해서는 Hashable 프로토콜을 채택해야한다.
아래 예시를 통해 더 자세히 살펴보자
struct Person {
let name: String
let gender: Gender
let identityNumber: Int
}
Person 타입의 객체 2개가 있다고 해보쟈
let jarry = Person(name: "제리", gender: .men, identityNumber: 1)
let lily = Person(name: "릴리", gender: .women, identityNumber: 2)
jarry와 lily를 비교할 때 단순히 생각하면 내부 요소(name, gender, identityNumber)를 비교한다고 생각할 수 있지만
실제로 해보면 안된다.
위에서 설명했듯이 Person은 개발자가 직접 만든 타입이기 때문에 Person이 Hashable하지 않기 때문에 비교할 수 없기 때문이다.
해결하기 위해 Hashable 프로토콜을 채택한다.
struct Person: Hashable {
let name: String
let gender: Gender
let identityNumber: Int
}
이렇게 Hashable을 채택하고
다시 jarry와 lily를 비교하면 오류메시지가 나타나지 않는다.
여기서 새로운 사용자 정의 타입인 House를 추가한다.
struct House {
let type: HouseType
let name: String
let address: String
let cost: Int
}
let jarryHouse = House(type: .apartment, name: "아파트", address: "서울", cost: 1000)
let lilyHouse = House(type: .building, name: "빌딩", address: "경기도", cost: 500)
print(jarry == lily)
이렇게 하면 새로운 오류메시지가 나타난다.
Person이 다시 Hashable하지 않게 되었다.
여기서 Fix를 눌러 == 함수를 추가한다.
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
}
하지만그럼에도 오류메시지는 사라지지 않는다.
struct House: Hashable {
let type: HouseType
let name: String
let address: String
let cost: Int
}
결론적으로 House가 Hashable하게 되어서야 오류 메시지는 사라진다.
Fix를 통해 == 함수를 추가하는것과 관계 없이 House를 Hashable로 해주어야 오류메시지가 사라졌다.
그러면 == 함수와 hash(into: )를 사용하지 않았는데 두 값을 비교 가능했다면 == 함수와 hash(into: )는 왜 필요한지 궁금해졌다.
우선 Hashable의 정의부를 가면 아래와 같다.
public protocol Hashable : Equatable {
/// The hash value.
///
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
///
/// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
/// conform to `Hashable`, implement the `hash(into:)` requirement instead.
/// The compiler provides an implementation for `hashValue` for you.
var hashValue: Int { get }
/// Hashes the essential components of this value by feeding them into the
/// given hasher.
///
/// Implement this method to conform to the `Hashable` protocol. The
/// components used for hashing must be the same as the components compared
/// in your type's `==` operator implementation. Call `hasher.combine(_:)`
/// with each of these components.
///
/// - Important: In your implementation of `hash(into:)`,
/// don't call `finalize()` on the `hasher` instance provided,
/// or replace it with a different instance.
/// Doing so may become a compile-time error in the future.
///
/// - Parameter hasher: The hasher to use when combining the components
/// of this instance.
func hash(into hasher: inout Hasher)
}
하나씩 살펴보겠당
hashValue
Hashable 프로토콜을 채택하면 hashValue를 구할 수 있게 되고, 이 hashValue는 요소들의 위치를 찾는 수단이 된다.
프로그램의 여러 실행에서 해시 값이 동일하다고 보장되지는 않습니다.
향후 실행 중에 사용할 해시 값을 저장하지 마십시오.
- 중요: hashValue는 Hashable의 필수 요소로 부터 deprecated 되었습니다.
대신 Hashable 요건을 구현하십시오.
컴파일러는 HashValue에 대한 구현을 제공합니다.
위 주석 내용처럼 hashValue는 프로그램이 실행될 때 마다 값이 계속 변한다. 따라서 따로 이 값을 저장해서 식별하는 등의 작업은 옳지 않다.
그리고 Swift5 기준으로 hashValue를 직접 설정해주는 것은 deprecated되었기 때문에 hash(into:)를 사용해야한다.
그리고 hashValue는 get만 가능하기 때문에 따로 값을 지정할 수는 없다.
hashValue는 원본 데이터를 특정 규칙에 따라 처리하여 간단한 숫자로 만든 것으로 실제로 값을 찍어보면 Int형의 값이 print되고, 이 값은 실행할 때 마다 변한다.
그러나 hashValue가 Int형으로 유한한 범위(2^32)이며, 데이터가 무수히 많아질 경우 서로 다른 데이터가 동일한 해쉬값을 가질 수 있다. 따라서 해쉬 충돌이 발생할 수 있다.
hash(into:)
https://developer.apple.com/documentation/swift/hashable/hash(into:)-v52
hash(into:) | Apple Developer Documentation
Hashes the essential components of this value by feeding them into the given hasher.
developer.apple.com
이 값의 필수 구성 요소를 주어진 hasher에 공급하여 해시합니다.
이 방법은 'Hashable' 프로토콜을 따르도록 구현합니다.
해시에 사용되는 구성 요소는 유형의 '==' 연산자 구현에서 비교되는 구성 요소와 동일해야 합니다.
이러한 각 구성 요소와 'hasher.combine(_:)'을 호출합니다.
- 중요: hash(into:)'를 구현할 때 제공된 hasher의 인스턴스에서 finalize()를 호출하거나 다른 인스턴스로 대체하지 마십시오.
그렇게 하면 나중에 컴파일 시간 오류가 발생할 수 있습니다.
- Parameter hasher: 이 인스턴스의 구성 요소를 결합할 때 사용할 hasher입니다.
이 메서드를 통해 식별 할 수 있는 identifier를 hasher에게 넘겨줄 수 있다.
사용자 정의 타입에서 Hashable을 채택하면 모든 프로퍼티가 Hashable을 채택했다면 hash(:into)메서드 자동 구현된다
클래스는 원칙적으로 Hashable 지원 불가하지만 인스턴스의 유일성을 갖게 하기 위해서는 hash(into:) 메서드 직접 구현해야한다.
열거형의 경우 연관값이 없다면 기본적으로 Equatable/Hashable하기 때문에 Hashable 프로토콜을 채택하지 않아도 된다.
struct Person: Hashable {
let name: String
let gender: Gender
let identityNumber: Int
let house: House
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
struct House: Hashable {
let type: HouseType
let name: String
let address: String
let cost: Int
}
해당 코드에서 각 프로퍼티가 모두 Hashable을 채택했기 때문에 hash(into:)를 구현하지 않아도 컴파일 에러가 나타나지 않는다.
다만 hash(into:)를 구현 하지 않으면 컴파일러가 자동으로 어떤 규칙에 따라 해시값을 생성하기 때문에 원하는 동작을 보장하지 않을 수 있다.
예를 들어 나는 name을 기준으로 값을 구분하고 싶었는데 그런 디테일한 요소(?)를 직접 설정하기 어렵다.. 머 그런 의미 같다.
따라서 예상할 수 있는 결과를 위해서 hash(into:)를 직접 구현하여 고유한 해시값을 생성함으로써 원하는 동작을 보장하게 할 수 있다.
Equatable protocol
public protocol Equatable {
/// Returns a Boolean value indicating whether two values are equal.
///
/// Equality is the inverse of inequality. For any values `a` and `b`,
/// `a == b` implies that `a != b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
static func == (lhs: Self, rhs: Self) -> Bool
}
Hashable은 Equtable 프로토콜을 준수하고 있다.
그 이유는 hashValue는 항상 unique하지 않기 때문이다.
위에서 말했듯 hashValue가 같으면 해시 충돌이 일어날 가능성이 있기 때문에 hashValue만으로는 정확도가 떨어져서 추가로 동일한지 확인하는 Equtable이 필요하다.
따라서 Hashable하게 만들어주려면 두가지가 필요하다.
1. == 함수
- Equtable 프로토콜
- hashValue가 같을 수 있기 때문에 이를 방지하기 위해 사용
2. hash(into: )
- Hashable 프로토콜
- hashValue를 만들기 위해 사용
- combine(value)에 들어가는 value는 == 함수의 구현에서 비교되는 요소와 동일해야 함
결과적으로 ==함수와 hash(into:)는 컴파일 에러가 발생하지 않는 한에서 구현하지 않아도 되지만 더 정확하고 예상 가능한 결과를 위해 구현해준다 라고 이해했다..!
이렇게 까지 하면 예상 가능한 결과를 얻을 수 있는 안전한..!? hashable를 할 수 있다.
출처
https://woongsios.tistory.com/145
https://applecider2020.tistory.com/14
'iOS > Swift' 카테고리의 다른 글
[iOS/Swift] Convenience init (0) | 2023.04.21 |
---|---|
[iOS] 타입 주석(Type Annotation)과 타입 추론(Type Inference) (0) | 2022.12.27 |
[iOS/Swift] CoreData 공부하기 (0) | 2022.08.23 |
[iOS/swift] navigation controller에서 root view 지정 (스토리보드 구성) (0) | 2022.08.20 |
Reactive X 공부 (0) | 2022.07.19 |