항상 프로토콜을 다양하게 사용하고 있음에도 정확한 의미나 원리 같은 것은 정리해 본 적이 없는 것 같아 프로토콜 뽀개볼까 시리즈로 정리해 볼까 합니다!! 당연해 보이는 걸 당연하게 넘기지 않는 연습을 좀 더 해볼까 해요~~
여느 때와 같이 나를 위한 정리랄까.. 타인에게 도움이 된다면 더할 나위 없이 좋습니닿ㅎ🤍
프로토콜 뽀개볼까의 첫 번째 프로토콜! Equatable입니당
📍공식문서
https://developer.apple.com/documentation/swift/equatable#overview
Equatable | Apple Developer Documentation
A type that can be compared for value equality.
developer.apple.com
일단 공식문서를 봐주자면
정의
"A type that can be compared for value equality" 즉, "값 동등성을 비교할 수 있는 형식입니다."라고 합니다.
여기서 그냥 동등성 아닌 “값”동등성(value equality) 이라고 말한 데는 이유가 있으니 기억해 놓기로 합시다.
Overview 파트를 훑어봅시다!
"Equatable 프로토콜을 준수하는 유형은 같음 연산자(==)를 사용하여 같은지 비교하거나 같지 않음 연산자(!=)를 사용하여 같지 않은지 비교할 수 있습니다."
(이것만 읽고 엇 근데 우리는 자연스럽게 == 써왔는데 ??했다면??)
"Swift 표준 라이브러리의 대부분의 기본 유형은 Equatable을 따릅니다."
(-> 이 이유 때문이죠 ㅎㅎ, String, Bool 등등 기본 유형 구현부에 가보면 Equatable을 채택하고 있는 것을 확인할 수 있습니다!)
"그리고 일부 sequence 및 collection operations은 요소가 Equatable을 준수하는 경우 더 간단하게 사용할 수 있습니다."
(아래 students라는 [String] 타입의 배열에다가 contains라는 메서드를 사용하고 싶을 때 내부 요소가 String, 즉, Equatable 프로토콜을 준수하기 때문에 동등성을 판단하기 쉽다는 것입니다)
let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]
let nameToCheck = "Kofi"
if students.contains(nameToCheck) {
print("\(nameToCheck) is signed up!")
} else {
print("No record of \(nameToCheck).")
}
// Prints "Kofi is signed up!"
그니까!! Equatable 프로토콜은 == 와 != 연산자를 써서 값의 동등성을 비교해 주기 위해 필요한 거라고 보고 일단 다음 내용으로 넘어가 봅시다!
📍Swift 기본 유형의 Equatable
위에서 언급한 것처럼 기본 타입은 모두 Equatable을 채택하고 있습니다. 이렇게요!
그렇기 때문에 기본 타입의 동등성을 비교할 때 바로 ==나 != 같은 연산자를 써서 값이 같은지 다른지 비교해 줄 수 있는 것이죠.
let str1 = ""
let str2 = ""
let int1 = 0
let int2 = 0
let bool1 = true
let bool2 = false
print(str1 == str2)
print(int1 == int2)
print(bool1 == bool2)
그치만 이런 기본타입들이 아니라 구조체나 클래스의 인스턴스를 비교하고 싶을 땐 어떻게 해야 할까요??
이 때는 그냥 인스턴스1 == 인스턴스2 이렇게 쓰면 에러가 납니다!
해당 구조체, 클래스 타입에 Equatable을 채택해주지 않았기 때문인데요!
이럴 때 우리는 어떻게 동등성을 비교할 수 있는지 알아볼까요!
📍구조체에서의 Equatable
struct1과 struct2가 같은 값을 가지는지 비교해보고 싶은데 아래처럼 하면 에러가 나버립니다..
🚨Binary operator '==' cannot be applied to two 'Struct' operands🚨 이런 에러가 뜨죠.
해당 타입에 == 연산자가 적용되지 않는다는 뜻인데요. 그럼 어떻게 동등성을 비교하게 만들어줄 수 있을까요?
이때 등장하는 게 바로 Equatable 프로토콜입니다!!
struct Struct {
var str : String
var int : Int
var bool : Bool
}
let struct1 = Struct(str: "string", int: 0, bool: true)
let struct2 = Struct(str: "string", int: 0, bool: true)
print(struct1 == struct2)
일단 Equatable 채택해봐 줄까요??
바로 에러가 사라지고 정상 동작하게 됩니다!! 잉 쉽네
사실 Equatable 프로토콜을 요구사항으로 ==라는 static 메서드 구현이 있는데요!
근데 왜!! 구현 안 했는데 그냥 에러 없어지고 == 연산자 사용이 가능해지는 거죠라고 한다면..죄송합니다 스위프트가 구조체는 구현 안 해도 된다고 하네요..라는 답변은 너무 무책임하고 ㅎㅎ
자세히 설명하면 구조체의 경우에는 인스턴스 안의 프로퍼티 값이 ‘모두’ 같을 때는 메서드 구현을 생략해도 되게 해놨다 입니다. 즉, str, int, bool에 대한 값이 모두 같은 인스턴스의 경우에만 동등하다고 판단하고 싶을 때는 메서드 만들지 않아도 되는 것이죠!
그래서! 이런 이유로! 구조체에서는 프로토콜을 채택하고 static func ==를 구현해주지 않더라도 바아로!! == 연산자를 쓸 수 있게 됩니다..
struct Struct : Equatable{
var str : String
var int : Int
var bool : Bool
}
let struct1 = Struct(str: "string", int: 0, bool: true)
let struct2 = Struct(str: "string", int: 0, bool: true)
print(struct1 == struct2) //true
근데 나는 어떤 특별한 이유 때문에 str, 이랑 int 프로퍼티만 값이 같아도 동등한 인스턴스라고 판단하도록 할래!!라고 한다면 이렇게 static 함수로 메서드를 구현해 주면 됩니다 :)
struct Struct : Equatable{
var str : String
var int : Int
var bool : Bool
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.str == rhs.str && lhs.int == rhs.int
}
}
let struct1 = Struct(str: "string", int: 0, bool: true)
let struct2 = Struct(str: "string", int: 2, bool: true)
print(struct1 == struct2) //false
📍클래스에서의 Equatable
클래스의 경우에도 이렇게 인스턴스를 비교하려 하면 에러가 나요..
Binary operator '==' cannot be applied to two 'Class' operands
class Class {
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: "string", int: 0, bool: true)
print(class1 == class2) //🚨 error 🚨
Equatable 준수하지 않는 클래스이기 때문인데! 그럼 구조체처럼 클래스에 Equatable만 채택해 주면 동작할까요??
클래스가 그렇게 만만할 거라고 생각했다면
어림도 없지 ㅎㅎ
Type 'Class' does not conform to protocol 'Equatable’
에러가 하나 사라지면 하나 생기는 코딩세계의 신비..
일단 저 에러는 왜 Equatable 채택했는데 필수 함수 구현 안 해주냐고 혼내는겁니당.. 에러를 자세히 열어보고 “fix” 버튼 누르면 뭐가 뿅 생기는데 ( “Fix” 버튼 있는 에러는 그래도 참 친절,,해서 좋다고 해야 하나 이거..)
== 함수를 구현해 주라고 코드 양식을 친절히 삽입해 주었습니다.
아까 Equatable 프로토콜을 요구사항으로 ==라는 static 메서드 구현이 있다고 말씀드렸는데요 클래스에서는 그게 필요한가 봅니다!
그럼 이제 우리는 “구조체에서는 이런 거 안 해줘도 됐단말야..;;까다로운 클래스.." 라고생각할 수 있는데
일단 왜 구조체는 자동 구현이 되는 걸까? 클래스는 왜 우리가 직접 구현해주어야 하는 걸까..에 대한 이유는 클래스까지 살펴보고 다시 설명하겠습니다 :(
일단 요구하는대로 == 메서드를 마저 구현해 봅시다.
class Class : Equatable{
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
}
}
그래도 구조체에서 한번 보고 온 함수니까 ==메서드 어떻게 구현해야 하는지 대충감이 오시져? 인스턴스를 비교할 때 str, int, bool 프로퍼티 모두 값이 같을 때 동등하다고 판단하게 하고 싶으면 위처럼 함수를 만들어주면 되겠죠
그럼 이제 이렇게 ==가 잘 동작합니다!
class Class : Equatable{
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: "string", int: 0, bool: true)
print(class1 == class2) //true
인스턴스 내부의 값이 다르다면 이렇게 false가 나오구요
let class1 : Class = Class(str: "string", int: 0, bool: true)
let class2 : Class = Class(str: "string", int: 1, bool: true)
print(class1 == class2) //false
📍열거형에서의 Equatable
클래스, 구조체까지 봐줬는데 열거형 안 봐주면 섭섭하죵
엥 ? 열거형도 Equatable이 있어야 == 가능하다고요??라는 의문이 든다면 보통 우리가 아래와 같이 연관값이 없는 열거형으로 사용을 많이 하기 때문일 거예요 이 경우에는 Equatable을 채택하지도 않아도 자동으로 구현이 됩니다.
아마 위에서 본 구조체, 클래스처럼 인스턴스를 만들어서 비교하는 게 아니라 어떤 “값”을 변수에 할당하고 비교해 주는 것이기 때문에 그런 것 같아요??
enum Enum {
case case1
case case2
case case3
}
let enum1 = Enum.case1
let enum2 = Enum.case1
print(enum1 == enum2) //true
하지만 연관값이 있는 열거형의 경우에는 동등성을 비교해 주려면 아래처럼 Equatable 채택을 해주어야 한다고 합니다.
근데!! 이 경우도 구조체처럼 == 메서드 직접 구현 안 해줘도 된다네욤! 구조체처럼 값 형식이기 때문에 바로 값과 값을 비교하게 되니까 그런 것 같아요!
enum Enum : Equatable {
case case1(caseNumber : Int)
case case2(caseNumber : Int)
case case3(caseNumber : Int)
}
let enum1 = Enum.case1(caseNumber : 1)
let enum2 = Enum.case1(caseNumber : 1)
print(enum1 == enum2) //true
📍 == 메서드의 자동구현
그럼 위에서 봤던 것처럼 왜 구조체는 == 메서드가 자동 구현되고 클래스는 우리가 직접 구현을 해주어야 하는 걸까....
https://babbab2.tistory.com/148#comment17021830
소들이님 이 어떤 분께 답변한 댓글을 참고했습니다 !👍
일단 Equatable 은 “값”이 같은지를 판별하는 동등성 비교에 필요한 프로토콜이라고 했습니다!
구조체에서는 메서드 구현해주지 않아도 “당연히 이 구조체 인스턴스들의 “값”을 비교하기 위해 프로토콜을 채택하는 것이겠구나”를 이미 알고 있어서 따로 개발자가 말해주지 않아도(메서드 구현해주지 않아도) 동등성 비교가 가능한 거죠?!
반면, 클래스는 Equatable 프로토콜 채택해 줘도 메서드를 직접 생성해줘야 동등성 비교가 가능해지는데 이것은 클래스가 참조타입이기 때문에 인스턴스의 000 프로퍼티의 값이 같을 때 동등하다고 해줄거야~~ 라고 말해줘야 하는 것이죠.
Equatable 프로토콜을 채택해 줘도 인스턴스가 갖는 “값”을 비교하기 위해서는 == 메서드가 필요한 것이라고 이해하면 될 것 같습니다 :)
클래스 인스턴스 비교에서 == 연산자와 === 연산자의 차이를 알면 좀 더 폭넓게 이해할 수 있는데요
- == 연산자 : 인스턴스 내부의 값이 같은지
- === 연산자 : 참조가 같은지 (동일한 메모리 주소를 가리키고 있는가)
==는 stack 영역의 "값"을 비교, ===는 heap 영역의" 메모리 주소"를 비교하는 겁니다.
아래 코드를 통해 === 는 참조가 같은지를 판별하는 연산자라는 것을 다시 한번 볼 수 있고
== 연산자는 당연히 에러가 납니다.. 클래스에서 Equatable 프로토콜을 준수해주지 않았고 == 메서드는 만들어주지 않았기 때문이죠!
class Class {
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: "string", int: 0, bool: true)
let class3 = class1 //⭐️ class1과 같은 메모리 주소 참조
print(class1 === class2) //false
print(class1 === class3) //true
print(class1 == class2) //🚨 error 🚨
이게 Equatable 프로토콜에 대해 감이 좀 오시나요??
틀린 내용 있다면 댓글로 피드백 주세요!!
'Swift' 카테고리의 다른 글
[Swift] 프로토콜 뽀개볼까 (3) | Codable (feat. Encodable & Decodable), JSON이란? (1) | 2024.04.28 |
---|---|
[Swift] 프로토콜 뽀개볼까 (2) | Comparable (1) | 2024.04.27 |
[Swift] Swift의 동시성 프로그래밍 (3) | async/await (WWDC 2021) (0) | 2024.04.07 |
[Swift] Swift의 동시성 프로그래밍 (2) | Swift Concurrency의 등장 (feat. async-await & Coroutine & Continuation) (0) | 2024.03.24 |
[Swift] Swift의 동시성 프로그래밍 (1) | GCD (0) | 2024.03.17 |