iOS - Equatable, Hashable Protocol

이한솔·2023년 11월 24일
0

iOS 앱개발 🍏

목록 보기
31/49

Equatable

Equatable 프로토콜은 Swift에서 프로토콜로, 해당 프로토콜을 채택한 유형은 == != 연산자를 사용하여 두 인스턴스 간의 동등성을 확인할 수 있다.


예시

Int나 String 같은 기본자료형은 Equatable이란 프로토콜이 이미 채택되어 컴파일러가 자동으로 구현해주기 때문에 비교연산자로 비교할 수 있다.

let num1 = 3
let num2 = 3
 
num1 == num2 // true
 
let str1 = "hi"
let str2 = "hello"
 
str1 == str2 // false

하지만 사용자 지정타입으로 직접 만든 클래스나 구조체 인스턴스를 비교연산자로 비교하려고 하면 에러가 발생한다.

struct Human {
    var name = ""
    var age = 0
}
 
let human1 = Human.init()
let human2 = Human.init()
 
human1 == human2     // Binary operator '==' cannot be applied to two 'Human' operands

Equatable Protocol의 정의

public protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool

Equatable 프로토콜은 하나의 요구사항만을 가지고 있다. 바로 == 연산자를 정의하는 것이다. 이 연산자는 두 개의 값(왼쪽 피연산자와 오른쪽 피연산자)이 동등한지 비교하고, 결과로 Bool 값을 반환한다.

따라서, 이 프로토콜을 채택하면 해당 타입에 대해 동등성 비교를 수행할 수 있는 것이다.


Equatable Protocol의 사용

구조체

구조체 내 모든 타입이 기본 타입이라면 직접 메서드를 구현해주지 않아도 Equatable을 채택하는 것만으로도 사용 가능하다. 해당 구조체 내의 모든 프로퍼티가 같아야 true를 반환한다.

struct Person: Equatable {
    var name: String
    var age: Int
}

let person1 = Person(name: "Alice", age: 30)
let person2 = Person(name: "Bob", age: 25)

if person1 == person2 {
    print("같은 사람입니다.")
} else {
    print("다른 사람입니다.")
}
// 출력값: 다른 사람입니다.

만약, name만 같아도 true를 반환하고 싶다면 직접 Equatable 프로토콜의 메소드를 구현해줘야 한다.

struct Person: Equatable {
    var name = ""
    var age = 0
    
    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.name == rhs.name
    }
}
 
let Person1 = Person.init()
let Person2 = Person.init(name: "", age: 19)
 
human1 == human2        // true

클래스

클래스는 직접 == 메서드를 구현해야 한다.

class Car: Equatable {
    var brand: String
    var model: String
    
    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }
    
    static func == (lhs: Car, rhs: Car) -> Bool {
        return lhs.brand == rhs.brand && lhs.model == rhs.model
    }
}

let car1 = Car(brand: "Toyota", model: "Corolla")
let car2 = Car(brand: "Toyota", model: "Corolla")

if car1 == car2 {
    print("같은 차종입니다.")
} else {
    print("다른 차종입니다.")
}

열거형

연관값이 없는 열거형
Equatable을 채택하지도 않아도 자동으로 구현된다.

enum Gender {
    case male
}
 
let man = Gender.male
let man2 = Gender.male
 
man == man2   // true

연관값이 있는 열거형
Equatable을 채택하면 구현된다. 메소드는 구현해주지 않아도 된다.

enum Gender: Equatable {
    case male(name: String)
}
 
let man = Gender.male(name: "so")
let man2 = Gender.male(name: "deul")
 
man == man2   // false


Hashable

Hashable은 Swift의 프로토콜로 해시값을 생성할 수 있는 유형을 나타낸다.
즉, Hashable 프로토콜을 채택한 유형은 해시 함수를 사용하여 고유한 해시값을 생성할 수 있다.

Swift의 Dictionary나 Set과 같은 해시 기반의 자료구조를 사용할 때, 해당 자료구조의 Key나 Value로 사용할 타입은 반드시 Hashable 프로토콜을 채택해야한다. 이는 중복된 키를 허용하지 않는 자료구조에서 요소를 식별하는 데 필요하다.


예시

Dictionary나 Set을 사용할 때 Key값으로 기본자료형을 사용할 때에는 기본 자료형은 컴파일러에 의해 Hashable 프로토콜이 알아서 채택되기 때문에 별도의 처리 없이 사용할 수 있다.

let dictionary: [String: Int] = ["one": 1]

하지만 사용자 지정 타입을 Key로 사용할 때는 Hashable 프로토콜을 직접 채택하지 않으면 어떤 값으로 해시를 해야할지 모르기 때문에 에러가 발생한다.

struct Human {
    let name: String
    let age: Int
}
 
let humanDict: [Human: Int]     // Type 'Human' does not conform to protocol 'Hashable'

따라서 Dictionary에서 Key값은 Hashable이란 프로토콜을 준수하고 있는 타입만 사용 가능하다.


Hashable Protocol의 정의

Hashable 프로토콜의 정의는 아래와 같다. Hashable 프로토콜은 Equatable 프로토콜을 준수한다.

public protocol Hashable: Equatable {
    var hashValue: Int { get }
    func hash(into hasher: inout Hasher)

💡 해쉬값은 왜 사용할까?
해시테이블에서 설명했듯이 값의 검색 속도가 빠르기 때문이다.
원래는 처음부터 순서대로 값을 찾아야해서 선형 검색의 시간복잡도는 O(n) 인데 해시테이블에서는 해시 값을 index로 사용하여 원하는 값의 위치를 한 번에 알 수 있기 때문에 시간복잡도는 O(1)이다.

0개의 댓글