티스토리 뷰

최종 프로젝트를 진행하다가 위 사진처럼

알림 탭에 예약내역과 사장님의 공지사항을 한 번에 보여줘야하는 상황이 생겼는데

2가지 다른 타입의 Model을 합쳐 하나의 ForEach문에 돌릴 때 어떤 Hashable을 사용해야할 지 몰라 에러가 났다.

 

구글링을 해보니... Swift에서 직접적으로 해시 테이블을 구현해서 사용할 일이 거의 없다고ㅎㅎ

그런데 사용할 필요가 없지만 커스텀하여 사용했다.ㅋㅋㅋㅋㅋㅋ

 

왜 사용했을까를 곰곰히 생각해보다가 Hashable이라는 것 자체를 잘 몰라서

그 땐 이게 최선이었던 것 같다.

 

우선 Hash라는 것부터 알아봐야겠다.

 

1. 해쉬란 데이터를 다루는 기법 중 하나

2. 데이터를 간단한 숫자로 표현

3. 임의의 크기를 가진 데이터(Key)를 고정된 크기의 데이터(Value)로 변화시켜 저장

4. 2개의 데이터를 비교할 때, 데이터가 동일하면 각 데이터의 해쉬값 동일

(해쉬 값이 동일하다고 데이터가 동일하다는 참이 아닐 수도 있다고 한다. 해쉬 값은 일정 크기의 Int값으로 유한하기 때문이다.)

.

.

.

 

 

오.. 데이터를 비교하기 위해 혹은 저장하기 위해 사용하는 거구나...

그럼 Hashable은?

 

공식문서에는 정수 해쉬 값을 생성하기 위해 Hasher로 해쉬될 수 있는 유형이다.

또한 Hashable은 Equatable이라는 프로토콜을 채택하고 있네?!

그래서 값 비교가 가능했구나...

 

일단 그래서 초기에 코드를 짤 때에는 시간이 촉박해서

우선 되게만 하려고 Hashable을 커스텀해서 사용을 했는데

지금 여유가 있을 때 다시 생각해보니 이미 Hashable 자체가 이 역할을 해주는 거여서 커스텀할 필요가 아예 없었는데

커스텀할 시간에 조금 더 차분히 살펴볼 걸 그랬다...

 

삽질과 나의 한심함을 느꼈다.

 

그래서 코드를 찬찬히 살펴보니 ReservationModel에는 Hashable이 채택되어있지 않네?ㅎㅎㅎㅎㅎ

 

덕분에 Hashable이 이런 식으로 구성되어있구나를 알아서 기록해본다.

커스텀하기 위해 식별할 수 있게 Identifiable, 그리고 Hashable 채택한 TestProtocol을 만들고

비교할 수 있게..  static func == (lhs: Self, rhs: Self) -> Bool { } 를 만들었네유...

근데 여러분은 삽질의 시간을 보는 것일 뿐... 바로 Hashable을 가져다쓰면 됩니다.

 

protocol TestProtocol: Identifiable, Hashable {
    var classification: String { get set }
    var id: String { get set }
    
    // 예약 현황
    var shopId : String { get set }
    var userId : String { get set }    // 이메일 형식으로 들어옴
    var reservedTime : Date { get set }
    var state : String { get set }
    
    // 샵 노티스 공지 사항
    var category : String { get set }
    var shopName : String { get set }
    var date : Date { get set }
    var title : String { get set }
    var body: String { get set }
    
    static func == (lhs: Self, rhs: Self) -> Bool
}

extension TestProtocol {
    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.id == rhs.id
    }
}

 

 

이렇게 만들어주고 ShopNotice와 ReservationModel을 합치기 위한... Model 작업을 해준다.

물론 이 MergedType도 Hashable을 통해 값 비교도 하고,, 데이터를 저장하기도 하고... 해야한다.

 

각각의 모델들은 다르고 값이 없을 수도 있기 때문에 옵셔널 처리할거다.

struct MergedType: Hashable {
        let shopNotice: ShopNotice?
        let reservation: ReservationModel?
        let type: String
        let date: Date
        
        func calculateTime(_ date: Date) -> String {
            let format = DateFormatter()
            format.locale = Locale(identifier: "ko_KR")
            format.dateFormat = "M월 d일"
            return getTimeName(Int(Date().timeIntervalSince(date)), format.string(from: date))
        }
        
        func getTimeName(_ time: Int, _ date: String) -> String {
            let result = time / 60
            switch result {
            case 0:
                return "방금"
            case 1 ... 59:
                return "\(result)분 전"
            case 60 ... 1439:
                return "\(result / 60)시간 전"
            default:
                return date
            }
        }
    }
    
    func mergedArray() -> [MergedType] {
        var result: [MergedType] = []
        for notice in filteredMyNotice() {
            result.append(MergedType(shopNotice: notice, reservation: nil, type: "notice", date: notice.date))
        }
        for reservation in filteredMyReservation() {
            result.append(MergedType(shopNotice: nil, reservation: reservation, type: "reservation", date: reservation.reservedTime))
        }
        return result.sorted(by: {$0.date > $1.date} )
    }

 

 

=================================================================================

 

하지만 이렇게 안써도 되고 아래 Model로 끝났다.

 

struct MergedType: Hashable {
        let shopNotice: ShopNotice?
        let reservation: ReservationModel?
        let type: String
        let date: Date
        
        func calculateTime(_ date: Date) -> String {
            let format = DateFormatter()
            format.locale = Locale(identifier: "ko_KR")
            format.dateFormat = "M월 d일"
            return getTimeName(Int(Date().timeIntervalSince(date)), format.string(from: date))
        }
        
        func getTimeName(_ time: Int, _ date: String) -> String {
            let result = time / 60
            switch result {
            case 0:
                return "방금"
            case 1 ... 59:
                return "\(result)분 전"
            case 60 ... 1439:
                return "\(result / 60)시간 전"
            default:
                return date
            }
        }
    }
    
    func mergedArray() -> [MergedType] {
        var result: [MergedType] = []
        for notice in filteredMyNotice() {
            result.append(MergedType(shopNotice: notice, reservation: nil, type: "notice", date: notice.date))
        }
        for reservation in filteredMyReservation() {
            result.append(MergedType(shopNotice: nil, reservation: reservation, type: "reservation", date: reservation.reservedTime))
        }
        return result.sorted(by: {$0.date > $1.date} )
    }

다시 깔끔해진 나의 코드들

쓴 코드들을 왜 리팩토링을 해야하는 지 알겠다.

 

이 2개로 깔끔하게 끝날 수 있었다...

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
TAG more
«   2025/05   »
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
글 보관함