Swift문법 - (26)주요프로토콜

Youth·2023년 4월 17일
0

swift문법공부

목록 보기
26/27
post-custom-banner

개발을 하다보니 Swift에 기본적으로 구현되어있는 프로토콜에 대한 이해가 필요할거같아서 이참에 한번 정리해보기로 했다

주요 프로토콜

  • Equatable / Comparable / Hashable
    • Equatable : ==, != 비교 연산자 관련 프로토콜
    • Comparable : < 연산자 관련 프로토콜 (>, <=, >=)
    • Hashable : hash값을 갖게되어 값이 해셔블(값이 유일성을 갖게됨)해짐
  • Int, Double, String은 연산자가 구현이 되어있지만 개발자가 만들어서 쓸수있는 타입(Enum Struct Class)는 구현을 해야할 수도 있음

Equatabel 프로토콜의 원칙

원칙)

  • 구조체, 열거형의 경우 Equatable 프로토콜 채택시 모든 저장 속성(열거형은 모든 연관값)이 Equatable 프로토콜을 채택한 타입이라면 비교연산자 메서드 자동구현

예외)

  • 1) 클래스는 인스턴스 비교를 하는 항등연산자(===)가 존재하기 때문에 비교연산자(==)
    구현방식에 대해 개발자에게 위임 (클래스는 원칙적으로 동일성(==) 비교 불가)
  • 2) 열거형의 경우 연관값이 없다면 기본적으로 Equatable/Hashable하기 때문에
    Equatable 프로토콜을 채택하지 않아도 됨

1. Equatable 프로토콜

  • Equatable 프로토콜의 요구사항은 static func == (lhs: Self, rhs: Self) -> Bool 메서드의 구현
  • 스위프트에서 제공하는 기본 타입은 모두다 채택을 하고 있음
💡 원칙: 동일성을 비교(==)하려면, Equatable을 채택 Equatable을 채택하면 비교연산자(==)메서드 자동구현
  1. 열거형(Enum)의 경우
  • 연관값이 기본제공타입인 경우 Eqatable 프로토콜을 채택만 해도 가능
enum SuperComputer: Equatable {
    case cpu(core: Int, ghz: Double)
    case ram(Int)
    case hardDisk(gb: Int)
}
SuperComputer.cpu(core: 8, ghz: 3.5) == SuperComputer.cpu(core: 16, ghz: 3.5)
SuperComputer.cpu(core: 8, ghz: 3.5) != SuperComputer.cpu(core: 8, ghz: 3.5)
  • 연관값이 없는 경우엔 Equatable을 채택하지 않아도 비교 연산 가능
enum Direction {
    case east
    case west
    case south
    case north
}
Direction.north == Direction.east    // false
Direction.north != Direction.east    // true
  1. 구조체(Struct)의 경우
  • 모든저장속성이 기본타입이면(사용자 지정타입이아니라면) 프로토콜만 채택
struct Dog {
    var name: String
    var age: Int
}

extension Dog: Equatable {}
  • extension으로 Equatable프로토콜을 채택해서 자동으로 비교연산자 가능
let dog1: Dog = Dog(name: "초코", age: 10)
let dog2: Dog = Dog(name: "보리", age: 2)

dog1 == dog2
dog1 != dog2
  1. 클래스(Class)의 경우
  • 클래스의 경우 ===연산자가 존재하기 때문에 1)Equatable채택 2)비교연산자 구현
class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
// 특별한 이유가 없다면 모든 속성에 대해, 비교 구현
extension Person: Equatable {
    static func ==(lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
        //return lhs.name == rhs.name     // 이름만 같아도 동일하다고 보려면 이렇게 구현
        //return lhs.age == lhs.age       // 나이만 같아도 동일하다고 보려면 이렇게 구현
    }
}
let person1: Person = Person(name: "홍길동", age: 20)
let person2: Person = Person(name: "임꺽정", age: 20)

person1 == person2
person1 != person2

Comparable 프로토콜의 원칙

원칙)

  • 구조체, 클래스의 모든 저장 속성(열거형은 원시값이 있는 경우)이 Comparable을 채택한 경우라도, <(less than)연산자 직접 구현해야함
  • (순서 정렬 방식에 대해서는 무조건 구체적인 구현이 필요하다는 논리)

예외)

  • 열거형의 경우, 원시값이 없다면(연관값이 있더라도) Comparable을 채택만 하면
    <(less than)연산자는 자동 제공
  • (원시값을 도입하는 순간, 개발자가 직접 대응되는 값을 제공하므로 정렬방식도 구현해야한다는 논리)

2. Comparable 프로토콜

  • Comparable 프로토콜의 요구사항은 static func < (lhs: Self, rhs: Self) -> Bool 메서드의 구현
  • 일반적으로 < 만 구현하면 <, <=, >= 연산자도 자동 구현
  • Comparable 프로토콜은 Equatable프로토콜을 상속하고 있음
    (필요한 경우, 비교연산자(==)도 구현해야함)
  • 스위프트에서 제공하는 기본 숫자 타입 및 String은 모두 다 채택을 하고 있음
    (Bool타입은 채택하지 않음)
  1. 열거형(Enum)의 경우
  • 원시값이 있다면 1)Comparable프로토콜 채택 2)less than 연산자 구현
enum Direction: Int {
    case east
    case west
    case south
    case north
}

extension Direction: Comparable {
    static func < (lhs: Direction, rhs: Direction) -> Bool {
        return lhs.rawValue < rhs.rawValue
    }
}

Direction.north < Direction.east    // false
Direction.north > Direction.east    // true
  • 원시값이 없다면(연관값이 존재하더라도) 프로토콜 채택만하면 비교연산 자동구현
  1. 구조체와 클래스의 경우
  • 1)Comparable프로토콜 채택 2)less than연산자 구현

Hashable 프로토콜

  • 딕셔너리의 key값 또는 Set의 요소는 Hashable 프로토콜을 채택한 타입이어야한다
  1. 열거형, 구조체의 경우
enum SuperComputer: Hashable {
    case cpu(core: Int, ghz: Double)
    case ram(Int)
    case hardDisk(gb: Int)
}

let superSet: Set = [SuperComputer.cpu(core: 8, ghz: 3.5),
									   SuperComputer.cpu(core: 16, ghz: 3.5)]

→SuperComputer의 경우 Set의 요소가 되기 위해서는 Hashable프로토콜 채택

struct Dog {
    var name: String
    var age: Int
}

extension Dog: Hashable {}

let dog1: Dog = Dog(name: "초코", age: 10)
let dog2: Dog = Dog(name: "보리", age: 2)

let dogSet: Set = [dog1, dog2]

→Dog의 경우 Set의 요소가 되기 위해서는 Hashable프로토콜 채택

  1. 클래스의 경우
  • 클래스는 인스턴스의 유일성 갖게 하기위해서는 hash(into:)메서드 직접 구현해야함(클래스는 원칙적으로 Hashable 지원 불가)
class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

Person타입을 Set의 원소로 넣으려면 Hashable프로토콜을 채택해야하는데 채택만하면 구현을 해주는게 아닌, 실제로 전부다 구현해야한다

extension Person: Hashable {
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(age)
    }
    
		**Hashable프로토콜을 Equatable프로토콜을 상속받기때문에 필수구현**
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
}
let person1: Person = Person(name: "홍길동", age: 20)
let person2: Person = Person(name: "임꺽정", age: 20)

let personSet: Set = [person1, person2]

CaseIterable 프로토콜

  • Enum타입에서 사용할 수 있는 CaseIterable
  • 열거형에서 CaseIterable 프로토콜을 채택하면 타입 계산 속성이 자동으로 구현됨
  • 연관값이 없는 경우에만 사용가능
enum Color: CaseIterable {
    case red, green, blue
}

→ 모든 케이스를 (정의한 순서대로) 포함한 배열을 리턴

Color.allCases     // [Color.red, Color.green, Color.blue]

CaseIterable 프로토콜 공식문서의 예제

// 공식문서의 예제
enum CompassDirection: CaseIterable {
    case north, south, east, west
}
  1. 케이스의 갯수를 세기 편해짐 (케이스를 늘려도 코드 고칠 필요 없음)
print("방향은 \(CompassDirection.allCases.count)가지")
  1. 배열로 return이 되므로 고차함수 이용 가능
// 배열을 문자열화
let caseList = CompassDirection.allCases
                               .map({ "\($0)" })
                               .joined(separator: ", ")
// result : "north, south, east, west"
  1. 랜덤 케이스를 뽑아낼 수 있음
let randomValue = CompassDirection.allCases.randomElement()

가위바위보 Case에서의 CaseIterable프로토콜 사용

enum RpsGame: Int, CaseIterable {
    case rock = 0
    case paper = 1
    case scissors = 2
}

let number = Int.random(in: 0...100) % 3 

// 나머지를 구하는 것이니 무조건 0, 1, 2 중에 한가지임
// 100대신 RpsGame.allCases.count를 넣어도됨
// 변수에 넣고 쓰면 더 깔끔한 코드로도 변형 가능
let number2 = Int.random(in: 0...100)%RpsGame.allCases.count
profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료
post-custom-banner

0개의 댓글