ABOUT ME

Today
Yesterday
Total
  • [SwiftUI] Hashable, Equatable, Identifiable
    Apple🍎/iOS 2024. 11. 10. 23:33

    struct에 Hashable이 선언된 곳 안에 또다른 데이터 타입으로 Hashable 상속을 안해주면 Equatable error 가 뜬다.

    Type 'Hike' does not conform to protocol 'Equatable

    이라는 에러로 시작해 글을 쓰게 됐다. 맨날 대충 찾아보고 긁어넣었던 이것들은 무엇일까?

    Hashable이란?

    hash 값을 생성할 수 있는 유형.
    즉, String, Int, Double, Float, Bool, Set 값들이 전부 해시값으로 바꿀 수 있는 것들이다.

    따라서 Hashable 프로토콜을 채택한 집합은 전부 이 유형을 갖고 있어야한다.
    설정 안한 열거형을 정의했을 때 자동으로 해시 가능성을 얻는다. 이러한 hash값을 사용하는 이유는, 탐색의 속도가 빨라서이다.
    해시 테이블은 hash index를 사용해서 내가 원하는 값을 한번에 찾을 수 있기 때문에 더 빠르게 찾을 수 있다.
    Hashable은 Equatable 프로토콜을 상속하므로 Equatable의 요구사항도 충족해야한다.

    상속받는 Equatable은?

    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
    }

    Equatable의 정의를 보면, ==, != 를 사용해 동등성 비교를 하는 프로토콜이다.
    Swift의 기본 데이터 타입(Int, Double, Float,,,) 도 Equatable을 준수하고 있기 때문에 값 비교가 가능해지는 것이다.

    그럼 struct, enum, class와 같은 사용자 타입에서는 어떻게 될까?

    struct Phone {
        var name: String
    }
    
    Phone(name: "iPhone") == Phone(name: "Zfilp")
    // Error : Referencing operator function '==' on 'Equatable' requires that 'Phone' conform to 'Equatable'

    이렇게 이런 식으로 구조체를 비교하면 에러가 발생한다.
    그러므로 struct, enum 의 경우 Equatable을 채택해주어야 비교가 가능해진다.

    struct Phone: Equatable {
        var name: String
    }

    하지만 class의 경우, 아래와 같은 함수를 추가로 넣어줘야한다.

    class Phone: Equatable {
        var name: String
        init(name: String) {
            self.name = name
        }
        
        static func == (lhs: Phone, rhs: Phone) -> Bool {
            return lhs.name == rhs.name
        }
    }
    
    Phone(name: "iPhone") == Phone(name: "Zfilp")
    // false

    Identifiable 이란?

    각 개체를 고유하게 식별하기 위해 사용하는 프로토콜이다.
    Identifiable을 채택한 구조체/클래스는 id라는 고유 식별자를 통해 개체를 구분할 수 있어야한다.
    주로 list나 데이터 바인딩을 할 때 많이 사용된다.

    protocol Identifiable {
        associatedtype ID: Hashable
        var id: Self.ID { get }
    }

    위와 같이 정의 자체에 변수 id가 있기 때문에, 구현해야한다.

    import SwiftUI
    
    struct User: Identifiable {
        let id = UUID()
        let name: String
    }
    
    let users = [
        User(name: "Alice"),
        User(name: "Bob"),
        User(name: "Charlie")
    ]
    
    // View
    struct ContentView: View {
        let users: [User]
    
        var body: some View {
            List(users) { user in // id를 통해 각 원소를 구분하기 때문에 안전하게 표시 가능하다.
                Text(user.name)
            }
        }
    }

     

    Identifiable을 사용하는 이유는?

    주된 이유는 SwiftUI에서 데이터 컬렉션의 각 항목을 고유하게 식별하기 위해서이다.

    List, ForEach 등의 뷰를 사용할 때 데이터가 고유하게 식별되지 않으면 UI가 예상대로 업데이트되지 않거나, 중복된 항목으로 인해 에러가 발생할 수 있기 때문.
    Identifiable을 사용하면 SwiftUI가 항목을 고유하게 구분하고, 데이터가 변경될 때 해당 항목만 부분적으로 업데이트할 수 있도록 돕는 특징이 있다.

    그러므로 아래와 같은 경우 Identifiable을 채택해 사용한다.

    • List, ForEach 같은 데이터 항목을 다룰 때
    • 동적인 데이터로, 데이터가 자주 변경되는 경우
    • 애니메이션을 사용할 때

    하지만 꼭 필요한 것은 아니다.

    ForEach(items.indices, id: \.self) { index in
                Text(items[index])
            }

    위와 같이 따로 지정해주고 사용할 수도 있다.

    요약

    • Hashable: 각 개체의 데이터에 해시값을 생성해 빠른 탐색이 가능하다. Equatable을 상속함.
    • Equatable: 객체의 동등성을 비교할 수 있게 하며, ==, != 연산자를 사용해 값 비교가 가능하다.
    • Identifiable: SwiftUI에서 각 항목을 고유하게 구분할 때 사용되며, List나 ForEach와 같은 반복 뷰에서 데이터를 안정적으로 관리할 수 있게 한다.
Designed by Tistory.