[Swift] Swift Equatable 완전 정복하기

2025. 4. 28. 20:15Swift, iOS Foundation

 

Equatable | Apple Developer Documentation

A type that can be compared for value equality.

developer.apple.com

 

Equatable을 언제 사용했더라?

아주 예전에 작성했던 글을 가지고 왔습니다.

[Swift] 제네릭 (Generic) 완전 정복하기라는 글에서 제네릭 타입에 특정 조건을 걸기 위한 기능, 제네릭 타입 제약 (Generic Type Constraints)을 설명하며
예시로 타입이 Equatable 프로토콜을 준수하도록 제약을 거는 코드를 소개한 적이 있었는데요.

예전 Generic Type Constraints에 사용한 Equatable을 확인해볼까요?


이 글에서는 Equatable이 "타입끼리 비교 연산을 하기 위해 = 즉, 비교 연산자를 사용하기 위해 필수적으로 채택해야 하는 프로토콜이다"까지만 소개하고 넘어갔었습니다.

여기서의 비교 연산자 (Comparison Operators)를 명확하게 정의하고 넘어가자면,

Equatable과 연관된 연산자는 같다 (==) / 다르다 (!=)에 대한 부분만 다루고 있습니다.
Equate는 "같게 하다" "동등하게 생각하다/다루다" "동일시하다"와 같은 단어로 번역된다는 점에서

가능성을 뜻하는 부분 "able"을 더해 Equatable은 결국 직역했을 때 "같게 할 수 있는" "동일시할 수 있는" 정도로 읽을 수 있겠군요!  

추가로 설명하면, 크다, 크거나 같다, 작다, 작거나 같다와 같은 세부 비교는 Comparable 프로토콜과 한 단계 더 연관되어 있습니다.
*그리고 Comparable은 기본적으로 "같음과 다름"에 대한 판단을 할 수 있어야하기 때문에 -> 내부적으로 Equatable을 채택하고 있습니다.

Equatable은 같다/다르다의 영역이라는 점!

 

 

Equatable 내부 코드 뜯어보기

💡 A type that can be compared for value equality = 값의 동등성비교할 수 있는 타입

냅다 Equatable 프로토콜이 코드로는 어떻게 정의되어있는지. 해당 부분을 살펴보겠습니다.
우선 코드는 public protocol Equatable 부분extension Equatable 부분으로 나누어서 볼 수 있을 것 같군요.

public protocol Equatable에는 기본적인 프로토콜과,
해당 프로토콜을 채택했을 때 반드시 구현해야하는 정적 메서드 == 부분이 명시되어 있습니다.

  • == 메서드는 타입 레벨에서 정의되어야 하는 비교 연산자이기 때문에 static을 붙였다. (a == b는 a 인스턴스의 메서드가 아니라, == 연산자가 호출하는 타입 메서드다.)
  • 비교 대상은 두개가 모두 같은 타입이어야 하기 때문에 lfs와 rhs도 모두 Self다. (Int와 Int, String과 String을 비교하는 것은 가능하지만, Int와 String을 비교하는 것은 불가능하다는 것을 의미한다고 볼 수 있죠.)


extension Equatable은 기본적으로 구현 (default implementation)되어 있다고 명시되어 있습니다.

즉, Equatable을 채택하는 모든 타입에 대해 != 타입 메서드를 기본적으로 제공한다는 의미죠.
그러니 저희들은 !=에 대한 구현없이 == 부분만 직접 정의해주면 될 겁니다!


아마도 해당 익스텐션에 정의되어있는 메서드 구현부는 아래와 같이 정의되어 있을 거고요!

public static func != (lhs: Self, rhs: Self) -> Bool {
    !(lhs == rhs)
}

 

 

그래서 Equatable이 뭔데?

코드를 뜯어보고 왔는데, 그래서 Equatable이 무엇인지 감이 오시는지요?

네. 쉽게 말하면 같다. 다르다를 판단할 수 있게 만들어주는 녀석입니다.
String, Int 등등... 기본 타입들은 같다 다르다를 우리가 자유롭게 비교할 수 있었잖아요? 내부적으로는 Equatable 프로토콜을 모두 채택하고 있었기 때문입니다.

그렇다면, 우리가 만든 커스텀 타입들...
Class나 Struct나 Enum 인스턴스에 대해서 같다/다르다를 판단하려고 한다면...?
아래와 같이 Binary operator '==' cannot be applied to two 'Account' operands라는 에러가 발생하고 있었는데요.


이때 필요한 것이 바로 Equatable입니다.

Account 구조체가 Equatable을 채택한다고 붙여주기만 했더니, 위의 에러가 사라지는 👀마법이랄까👀

Equatable만 붙으면 문제 해결!


구조체 (Struct)나 열거형 (Enum)의 경우에는 별도의 == 타입 메서드 정의 없이,
모든 프로퍼티가 Equatable을 채택하고 있는 타입인 경우라면 -> Equatable만 붙여주는 것만으로도 비교가 가능하게 됩니다.

하지만 클래스 (Class)의 경우는 조금 다릅니다.

아까 위에서 봤던 Equatable 프로토콜 정의부에 있던 메서드 == 부분까지 꼭 필수로 구현해야만 하죠.
!= 부분은 구조체와 마찬가지로 구현하지 않아도 되긴 합니다.

💬 클래스가 Equatable을 채택했을 때 == 메서드를 별도로 구현해야 하는 이유에 대한 생각
 -
값 타입 (Value Type)인 구조체와 열거형과는 다르게 클래스는 참조 타입 (Reference Type)이죠.
즉, 구조체와 열거형은 "프로퍼티 값이 동일한지?"만 단순하게 비교하면 되지만
클래스는 이 두 개가 "같은 값인지?" "같은 메모리 주소를 갖는 같은 객체인지?"를 구분해줄 필요가 있었던 것입니다!
*참고로, 값 비교는 == 연산자로, 객체 비교 (= 메모리 주소 비교)는 === 연산자로 구분해서 할 수 있었습니다.

static func == 부분을 명시적으로 작성한 것을 집중해서 보면 됩니다!


여기까지 글을 이만 정리해 볼까 합니다!

  • Equatable은 값이 동일한지? 값이 다른지?를 비교하기 위해 채택할 수 있는 프로토콜이다.
  • String이나 Int는 우리는 몰랐지만, 내부적으로 Equatable을 채택하고 있었기에 우리가 비교 연산자 (==, !=)를 사용하고 있었던 것.
  • 구조체 (Struct)와 열거형 (Enum)은 프로퍼티 타입이 Equatable을 따른다면, 별도의 메서드 정의없이 Equatable을 붙어주는 것만으로도 사용할 수 있지만 / 클래스 (Class)는 조금 다르더라.