Swift

Swift) Enumeration

kangwook 2022. 10. 4. 23:49

Enum이란

컴퓨터 프로그래밍에서 열거형은 요소, 멤버라 불리는 명멷뇌 값의 집합을 위루는 자료형.

열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자.

쉽게 말하면 상수 역할의 값들을 보기 쉽게 나열해 놓은 것

Raw Values(원시 값)

enum의 case는 모두 독립적인 값이지만 내부에 또 다른 값을 저장할 수 있음 → raw value

enum Name : RawValueType {
    case caseName = value
}
  • 원시 값 타입으로 올 수 있는 것은 String, Character, Number Type
  • 선언 지점에 저장한 원시 값은 나중에 바꿀 수 없음
  • 원시 값을 저장하는 부분은 생략도 가능, 각각의 자료형마다 규칙이 존재
enum CompassPoint: Int {
    case north
    case south
    case east
    case west
}
 
CompassPoint.north.rawValue     // 0
CompassPoint.south.rawValue     // 1
CompassPoint.east.rawValue      // 2
CompassPoint.west.rawValue      // 3
  • 또한 원시값을 통한 case매칭으로 enum을 생성할 수 있음
CompassPoint(rawValue: 0)   // north
// 동일한 rawValue를 가진 north가 생성
 
CompassPoint(rawValue: 200) // nil
// 없는 경우에는 nil을 리턴

String Type

enum Weekday: String {
    case sunday
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}
 
Weekday.sunday.rawValue     // "sunday"
  • String으로 선언할 경우 원시 값 할당을 생략하면 case이름과 동일한 문자열이 원시값으로 저장
  • 직접 할당할 수도 있음

Character Type

enum ASCII: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}
  • Character 같은 경우 원시 값을 직접 할당해주지 않으면 컴파일 에러 발생

Associated Values(연관 값)

원시값의 한계

  • 모든 케이스가 동일한 형식을 사용해야 함
  • 케이스당 값을 하나밖에 저장할 수 없음
  • 원시값 문자열에 숫자가 포함되어 있을 경우 숫자만 사용하려면 따로 추출해야하는 번거로움이 있음
enum Name {
    case caseName(Type)
    case caseName(Type, Type, ...)
}
  • 원시 값의 경우 열거형 이름 뒤에 선언하지만, 연관 값은 케이스 이름 뒤에 선언
  • 튜플을 사용하여 하나의 케이스에 서로 다른 연관 값들을 저장할 수 있음
enum AppleDevice: String {
    case iPhone = "X, 256GB"
    case iMac = "27, Pro, 300만원"
    case macBook = "Air, 1kg, 150만원"
}
enum AppleDevice {
    case iPhone(model: String, storage: Int)    // named tuple
    case iMac(size: Int, model: String, price: Int)
    case macBook(String, Int, Int)              // unnamed tuple
}
 
var gift = AppleDevice.iPhone(model: "X", storage: 256)
 
switch gift {
case .iPhone(model: "X", storage: 256):
    print("iPhone X and 256GB")
case .iPhone(model: "X", _):
    // 와일드 카드 패턴
    print("iPhone X")
case .iPhone:
    // 연관 값 생략 가능
    print("iPhone")
case .iPhone(let model, let storage):
    // 블록 내부에서 연관값을 사용할 땐 상수로 바인딩
    // 값을 변경할 때는 var 로 변경 가능
    print("iPhone \(model) and \(storage)GB")
case let .iMac(size, model, price):
    // 모든 연관 값을 동일한 형태로 바인딩한다면
    // let 키워드를 열거형 케이스 앞에 표기하는 것도 가능
    print("iMac \(size), \(model), \(price)")
}
 
// 새로운 케이스를 할당할 경우 모두 새로운 값으로 교체
gift = .macBook("Air", 1, 150)
 
if case let .macBook(model, weight, price) = gift {
    print("macBook \(model), \(weight)kg, \(price)")
}

Enum with Protocol

protocol MenuProtocol {
    var menuName: String { get }
    mutating func nextMenu() -> Self
}
  • 프로퍼티는 enum에서 연산 프로퍼티만 사용할 수 있기 때문에 { get } 만 존재
enum FastFoodMenu: MenuProtocol {
    case chicken
    case pizza
    case hamburger
 
    var menuName: String {
        switch self {
        case .chicken:
            return "치킨"
        case .pizza:
            return "피자"
        case .hamburger:
            return "햄버거"
        }
    }
 
    func nextMenu() -> FastFoodMenu {
        switch self {
        case .chicken:
            return .pizza
        case .pizza:
            return .hamburger
        case .hamburger
            return .chicken
        }
    }
}
 
enum NoodleMenu: MenuProtocol {
    case ramen
    case pasta
    case udon
 
    var menuName: String {
        switch self {
        case .ramen:
            return "라멘"
        case .pasta:
            return "파스타"
        case .udon:
            return "우동"
        }
    }
 
    func nextMenu() -> NoodleMenu {
        switch self {
        case .ramen:
            return .pasta
        case .pasta:
            return .udon
        case .udon
            return .ramen
        }
    }
}
 
enum BeverageMenu: MenuProtocol {
    case coke
    case sprite
    case water
     
    var menuName: String {
        switch self {
        case .coke:
            return "콜라"
        case .sprite:
            return "사이다"
        case .water:
            return "물"
        }
    }
     
    func nextMenu() -> BeverageMenu {
        switch self {
        case .coke:
            return .sprite
        case .sprite:
            return .water
        case .water:
            return .coke
        }
    }
}
var vendingMachine = BeverageMenu.coke
print(vendingMachine.menuName)      // "콜라"
 
vendingMachine = vendingMachine.nextMenu()
print(vendingMachine.menuName)      // "사이다"
 
vendingMachine = vendingMachine.nextMenu()
print(vendingMachine.menuName)      // "물"

extension을 사용해서 케이스와 메서드, 프로퍼티를 분리하여 가독성을 높일 수 있음

enum FastFoodMenu: MenuProtocol {
    case chicken
    case pizza
    case hamburger
}
 
extension FastFoodMenu: MenuProtocol {
    var menuName: String {
        switch self {
        case .chicken:
            return "치킨"
        case .pizza:
            return "피자"
        case .hamburger:
            return "햄버거"
        }
    }
 
    func nextMenu() -> FastFoodMenu {
        switch self {
        case .chicken:
            return .pizza
        case .pizza:
            return .hamburger
        case .hamburger
            return .chicken
        }
    }
}

Protocol자체를 extension과 함께 사용함으로써 가독성을 더욱 높일 수 있음

protocol MenuProtocol {
    var menuName: String { get }
    func nextMenu() -> Self
}
 
extension MenuProtocol {
    var menuName: String {
        return String(describing: self)
    }
}

Generic Enumeration(제네릭 열거형)

제네릭으로 구현한 열거형의 대표적인 예는 Optional

Apple Documentation : Optional

 

Apple Developer Documentation

 

developer.apple.com

enum Optional<Wrapped> {
    case none           // 값이 없을 경우
    case some(Wrapped)  // 값이 있을 경우
}
 
let someValue = Optional<String>.some("some")
let nilValue = Optional<String>.none
 
print(someValue)    // Optional("some")
print(nilValue)     // nil

Recursive / Indirect Enumeration(재귀적/간접적 열거형)

재귀 / 간접 타입을 사용하면 열거형의 각 항목의 연관 값으로 열거형 타입을 지정 가능

자기 자신을 참조하는 열거형은 무한한 크기를 가질 수 있기 때문에 해당 타입이 재귀 / 간접 타입이라는 것을 명시해주어야 함

indirect enum LinkedListItem<T> {
    case end(value: T)
    case node(value: T, next: LinkedListItem)
}
enum LinkedListItem<T> {
    case end(value: T)
    indirect case node(value: T, next: LinkedListItem)
}
let cNode = LinkedListItem.end(value: "c")
let bNode = LinkedListItem.node(value: "b", next: cNode)
let aNode = LinkedListItem.node(value: "a", next: aNode)
 
var current = aNode
 
linkedListLoop: while true {
    switch current {
    case let .end(value):
        print(value)
        break linkedListLoop
    case let .node(value, next):
        print(value)
        current = next
    }
}
 
/**
a
b
c
**/

Custom Constructor(커스텀 생성자)

API를 통해 String, Int값을 받아온 후 열거형으로 변환하여 사용하려고 할 때 사용

구조체나 클래스의 생성자를 만드는 방법과 동일 → init

enum ErrorCode {
    case Code400
    case Code404
    case Code500
    case CodeNil
 
    init(errorCode: Int) {
        switch errorCode {
        case 400:
            self = .Code400
        case 404:
            self = .Code404
        case 500:
            self = .Code500
        default:
            self = .CodeNil
        }
    }
 
    var errorMessage: String {
        switch self {
        case .Code400:
            return "서버 요청 실패"
        case .Code404:
            return "페이지가 존재하지 않음"
        case .Code500:
            return "서버 응답 없음"
        case .CodeNil:
            return "알 수 없는 에러"
        }
    }
}
 
let errorCode = ErrorCode.init(errorCode: 404)
print(errorCode.errorMessage)
/** 페이지가 존재하지 않음 **/
 
let errorCode2 = ErrorCode.init(errorCode: 700)
print(errorCode2.errorMessage)
/** 알 수 없는 에러 **/

 

- 참조되어 있는 블로그들과 애플 공식 문서를 통해 공부했습니다.

 

참조