디자인 패턴에 대해서 공부하던중 예시 코드에서 Hashable을 채택하는 코드를 보고 먼저 학습을 해야겠다고 생각했다.
블로깅 결과 소들이 형님이 너무 자세하게 설명을 해주셔서 대부분 그의 블로그를 참조해서 작성하기로 했습다.
리산 알 가입 이시여...
Hash란?
딕셔너리에서 많이 사용되는 개념인 Hash는 Key - Value로 값을 저장하는 형태로 HashTable도 당연히 key - Value로 값을 저장합니다.
HashTable이란?
해시 테이블은 내부적으로 배열로 구현되어 있습니다.
key값은 해시 함수를 통해서 해시 주소값으로 변환되고
해시 주소값을 이용하여 해당하는 해시 테이블에 접근하여 value를 가져오거나 저장하는 구조입니다.
그런데 이러면 일반 배열에 값을 저장하는거랑 크게 다르지 않나? 그냥 배열쓰지 왜 이걸사용하지?
라는 생각을 하는 순간그에 대한 답을 우리 소들이 행님이 또 적어주심
간단히 설명하자면 순서대로 저장되는 일반 배열과는 다르게 해시 테이블의 경우 순서를 지키지 않고 저장됨
그러면 해시 테이블에 주소에 접근하여 저장 혹은 가져오기 위해서는 key값을 통해서만 접근이 가능한거 ㅎㅎ
사용 방법
아래는 내가 사용하려는(내가 짠건 아님)예제 코드인데 왜 Hashable을 채택하게 되면 hash 함수 그리고 == 메서드 또한 구현해야 되는가?
struct Customer {
let identifier: String
var name: String
var address: String
}
extension Customer: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: Customer, rhs: Customer) -> Bool {
return lhs.identifier == rhs.identifier
}
}
hasher.combine은 어떤 파라미터를 해시할 것인지를 넣어주는것
또한 특별한 경우가 아니면 combine에는 해당 타입의 모든 저장 프로퍼티를 전달해야된다.
라고 했는데 나는 왜 그런지 아직 모름... 그래서 그냥 identifier만을 combine해서 사용
그럼 == 메서드는 뭐지? 저걸 왜 하는거지?
hashable 클래스를 자세히 확인해보면 다음과 같습니다.
public protocol Hashable : Equatable {
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
}
아 hash라는 프로토콜을 채택하니깐 당연히 필요하겠구나 생각했는데 == 메서드를 지워도 오류가 나타나지 않았음
당연히 오류일거라고 생각했는데 알고 보니 구조체의 경우 Hashable을 채택하는 것만으로 Customer 구조체를 Dictionary의 Key로 사용하는 것이 가능했습니다!!!
ㅎㅎ 구조체를 사용할때는 Hashable을 채택만 해줘도 된다는것을 알아냈습니다!
그렇다면 Customer를 클래스로 바꾸면?
바로 Equatable과 Hashable 프로퍼티를 준수하지 않았다고 에러를 출력하네요
아하 구조체는 값타입으로 저장되는 반면 class의 경우 주소값이 저장되기 때문에 key값에는 주소가 들어가게 되면 해당 클래스를 여러번 사용하는 순간 Hash 함수에서 값에 대한 접근이 어렵겠구나
그럼 Equatable을 채택하는 이유는 알겠는데 왜쓰는걸까?
리산 알 가입께서는 해시 테이블 충돌시 Chaining 기법(해시 테이블)의 경우 같은 index에 key-value 값을 연결 리스트를 이용해서
저장하기 때문에 나중에 Value 값을 연결 리스트를 이용해서 저장하기 때문에 Value를 꺼낼 떄 같은 index에 저장된 연결 리스트 노드들 중에 고유한 값인 key를 비교(==)하며 맞는 Value를 꺼내기 위해서라고 생각하신다고 하셨다.
확실하게 알려면 더 찾아봐야 할거 같음...
구현
// Product는 구조체
class OrderList {
var orderList: [Customer: [Product]] = [:]
}
class Customer {
var identifier: String
var name: String
var address: String
init(identifier: String, name: String, address: String) {
self.identifier = identifier
self.name = name
self.address = address
}
}
extension Customer: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: Customer, rhs: Customer) -> Bool {
return lhs.identifier == rhs.identifier
}
}
class Order {
var customer: Customer
var product: Product
init(product: Product, customer: Customer) {
self.customer = customer
self.product = product
}
func order() {
var orderList = orderDB.orderList[customer, default: []]
orderList.append(product)
self.orderDB.orderList[customer] = orderList
guard let result = self.orderDB.orderList[customer] else {
return
}
print(result)
}
}
// Product 구조체 만드는거 생략함
let iphone = Product(identifier: "product-03", name: "iPhone", cost: 1000000)
let gnoam = Customer(identifier: "customer-01", name: "Gnoam", address: "신림")
let order = Order(product: iphone, customer: gnoam)
order.order()
// 결과
// identifier: "product-03", name: "iPhone", cost: 1000000)
결론
Hashable을 사용하면 구조체 혹은 클래스를 Key값으로서 value에 접근할 수 있는 코드로 구현이 가능하다
또한 클래스의 경우 Hashable을 채택하면 == 메서드와, hash 메서드를 추가로 구현해줘야한다.
출처
'iOS' 카테고리의 다른 글
[iOS] Concurrency(async & await) (0) | 2024.03.31 |
---|---|
[iOS] 새싹 프로젝트 (BoxOffice STEP3) #2 CollectionView 구현 (0) | 2024.03.24 |
[iOS] 새싹 프로젝트 (BoxOffice STEP3) #1 코드 베이스 화면 작업 (2) | 2024.03.15 |
[iOS] 새싹 프로젝트 (BoxOffice STEP2) (1) | 2024.03.01 |
[iOS] UserDefaults (0) | 2024.02.23 |