Swift

[Swift] 프로토콜 뽀개볼까 (2) | Comparable

하이D:) 2024. 4. 27. 19:38

지난번에 이어서 프로토콜에 대해 자세히 살펴볼까합니다!

 

 

저번 글에서 설명한 Equatable이 동등성 비교(==, != 연산자 사용)를 위해 필요한 프로토콜 이라면

Comparable은 <, <=, >=, > 연산자를 사용하여 대소를 비교하기 위해 필요한 프로토콜인데요!

 

 

일단 공부의 시작인 공식문서부터 볼까요

 

 

 

 

 

📍공식문서

A type that can be compared using the relational operators <, <=, >=, and >.

protocol Comparable : Equatable

 

정의를 보면 이 프로토콜을 채택함으로서 <, <=, >=, > 연산자를 쓸 수 있다고 하네요!

 

 

 

근데 Comparable은 Equatable을 채택하고 있네요..?

그렇다... 우린 Comparable을 준수하는 타입을 만들면 자연스레 Equatable도 준수하는 타입을 만들어야하는 것이다!!

 

Comparable을 준수하면 대소비교, 동등성 비교 모두 가능해진다는 것을 알 수 있어요. 즉,  <, <=, >=, > 연산자들은 물론 ==, != 연산자를 모두 쓸 수 있다는 것!!

 

 

 

 

📍기본타입에서의 Comparable

근데 우리 String, Int, Double 과 같은 타입은 기본적으로 그냥 대소비교 해주고있었잖아요?

print("abcd">"abcde")
print(1>2)

 

이렇게요. 우린 String 타입이나 Int 타입에 따로 Comparable을 준수하도록 설정해준 적이 없는데 어떻게 이게 가능한가 하면!

Equatable 처럼 기본 자료형은 이미 swift에서 다 알아서 세팅해놓았다는 말씀.

 

그럼 대충 감이 오는게 커스텀 타입인 구조체, 클래스, 열거형은 대소비교해주려면 뭔가 작업을 해주어야겠죠!

Equatble 처럼 Comparable 준수하도록 만들어줘야합니다.

 

 

 

 

📍구조체에서의 Comparable

이렇게 그냥 구조체에 대소비교 연산자 쓰면 에러가 납니다.

🚨 Binary operator '>' cannot be applied to two 'Struct' operands

 

그래서 Comparable 채택해줬는데도 에러가 난다???

🚨 Type 'Struct' does not conform to protocol 'Comparable'

 

 

 

 

뭐지? 구조체에서 Equatable은 그냥 채택만 해줘도 됐는데..

Equatable과 다르게 Comparable는 구조체도 기본구현 메서드를 구현해줘야합니다!

 

귀찮아.. Equatable처럼 프로퍼티 값 모두 비교하는 것으로 기본 구현 그냥 해주면안되나? 생각할 수 있지만

Swift에서 개발자에게 어떤 프로퍼티의 값을 비교할건지 정중하게 물어보는 것이라고 생각하고 컴 다운해봅시다. :)

 

 

 

 

에러알림에서 Fix 버튼 눌러보면 < 메서드 구현하라고 나오는데요!

 

이처럼 Comparable을 채택한다고 <, <=, >=, > 에 대한 메서드를 다 구현해주는게 아니라, 우리가 < 메서드만 구현해주면 알아서 다른 연산자도 대소비교 가능하도록 해줍니다!!

우리는 < 메서드를 통해 대소비교 연산자를 썼을 때 어떤 프로퍼티들을 비교할건지 기준만 설정해주는 것이죠 ㅎㅎ

 

struct Struct : Comparable{
    static func < (lhs: Struct, rhs: Struct) -> Bool {
        return lhs.str<rhs.str && lhs.int<rhs.int //str 과 int 모두 커야 큰 것으로 판별
    }
    
    var str : String
    var int : Int
    var bool : Bool

}

let struct1 = Struct(str: "string", int: 0, bool: true)
let struct2 = Struct(str: "string2", int: 2, bool: true)

print(struct1<struct2) // true

 

 

그럼 이렇게 대소비교를 할 수 있게 됩니닷

 

 

 

 

📍클래스에서의 Comparable

 

클래스에서도 한번 사용해볼까요.

Comparable 채택해주고 봤더니 이건 또 에러가 2개나 뜨네..’;;

 

 

 

 

일단 차근차근 Fix 버튼 눌러줘봅시다.

하나는 < 메서드 구현하라고 나오고 ( 이건 뭐 구조체에서도 했으니 예상 했던거!!)

하나는 == 메서드를 구현하라고 나옵니다.. 이건 또 뭔

 

 

 

 

 

자 근데 생각해보면 Comparable의 공식문서를 봤을 때 Comparable 자체가 Equatable을 준수하고 있어서 아까 설명할 때 Comparable를 준수하면 자동으로 Equatable를 준수하게 되는거라고 말씀드렸잖아요?

 

Equatable 글에서 봤을 때 Equatable를 준수하려면 클래스는 == 메서드 구현이 필요하다고 했죠..(참조가 아닌 값을 비교하기 위해 개발자가 직접 구현해서 비교하는 프로퍼티 값을 특정해준다)

 

어떻게 보면 당연하게 구현해줘야하는거였네요!

class Class : Comparable{
    static func < (lhs: Class, rhs: Class) -> Bool {
        return lhs.str<rhs.str && lhs.int<rhs.int //str 과 int 모두 커야 큰 것으로 판별
    }
    
    static func == (lhs: Class, rhs: Class) -> Bool {
        return lhs.str==rhs.str && lhs.int==rhs.int && lhs.bool==rhs.bool
    }
    
    var str : String
    var int : Int
    var bool : Bool

    init(str: String, int: Int, bool: Bool) {
        self.str = str
        self.int = int
        self.bool = bool
    }

}

let class1 : Class = Class(str: "string", int: 0, bool: true)
let class2 : Class = Class(str: "string2", int: 1, bool: true)

print(class1<class2) //true
print(class1>class2) //false
print(class1==class2) //false

 

 

위 코드를 보면 저는 동등성 비교할 때는 str, int, bool 프로퍼티가 모두 같아야 동일하게 본다고 했고

대소비교할 때는 str과 int 두 값이 모두 클 경우에 큰 인스턴라고 판별할것이라는 코드를 작성해주었습니다 :)

 

 

 

 

 

 

📍열거형에서의 Comparable

열거형도 안봐주면 섭하니 간단하게 보면

 

연관값 없는 열거형은 Comparable 만 채택해줘도 대소비교가 됩니다.

아니 case 끼리 어떻게 대소비교를해.. 한다면 밑으로 가는 case일수록 크다고 인식하더라구요!

enum Enum : Comparable {
    case case1
    case case2
    case case3

}

let enum1 = Enum.case1
let enum2 = Enum.case2

print(enum1 < enum2) // true

 

 

 

연관값이 있어도 딱히 <함수 구현해주는게 아니라 Comparable 만 채택해주면되네요??

enum Enum : Comparable {
    case case1(caseNumber : Int)
    case case2(caseNumber : Int)
    case case3(caseNumber : Int)

}

let enum1 = Enum.case1(caseNumber : 1)
let enum2 = Enum.case1(caseNumber : 2)
let enum3 = Enum.case2(caseNumber : 2)

print(enum1 < enum2) // true
print(enum2 < enum3) // true