iOS

[Swift] 스위프트 프로그래밍 - Part1 - 3 - 2 : 데이터 타입 고급(열거형 enum)

건복치 2021. 1. 19. 08:19
반응형
야곰님의 스위프트 프로그래밍 책으로 공부한 내용을 잊어버리지 않게 간단하게 정리한 글입니다.

관련 포스트

[Swift] 스위프트 프로그래밍 - Part1 - 1 : 스위프트? / 스위프트 장점 / 특징 / 명명 규칙 / 콘솔 로그 / 주석
[Swift] 스위프트 프로그래밍 - Part1 - 2 : 변수 / 상수 / 데이터 타입 기본 (Int, Bool, Float, Character, String, Any, AnyObject, nil)
[Swift] 스위프트 프로그래밍 - Part1 - 3 - 1 : 데이터 타입 고급 (Tuple, Array, Dictionary, Set)
[Swift] 스위프트 프로그래밍 - Part1 - 3 - 2 : 데이터 타입 고급(열거형 enum)

책 범위 : Part 1. 스위프트 기초 (4. 데이터 타입 고급 - 4.5 열거형)


열거형

  • 연관된 항목들을 묶어서 표현할 수 있는 타입
  • 배열, 딕셔너리와는 다르게 프로그래머가 정의해준 항목 값 이외에는 추가/수정이 불가 -> 정해진 값만 속할 수 있음
  • 각 항목은 값을 가질 수도, 가지지 않을 수 도 있다.
  • 값을 가질 경우 각 항목의 값은 원시 값(정수, 실수, 문자 등)의 형태로 실제 값을 가질 수도 있고, 연관 값을 사용해 다른 언어에서 공용체라고 불리는 값의 묶음도 구현할 수 있음

 

열거형을 유용하게 사용할 수 있는 경우

  • 제한된 선택지를 주고 싶을 때
  • 정해진 값 이외에는 입력받고 싶지 않을 때
  • 예상된 입력 값이 한정되어 있을 때

 

1. 기본 열거형

  • enum 키워드로 선언
  • 각 항목은 그 자체가 고유의 값
  • 항목이 여러가지일 경우 한 줄에 나열해 표현 가능
// 열거형의 선언
enum School {
    case elementry
    case middle
    case high
    case university
}

enum School2 {
    case elementry, middle, high, university // 위의 표현 한 줄로
}

// School 열거형 변수의 생성 및 값 변경 
var highestEducationLevel: School = School.university
var highestEducationLevel2: School = .university // 위와 같은 표현

highestEducationLevel = .high // 같은 타입인 School의 내부 항목으로만 값 변경 가능

 

2. 원시 값

  • 열거형의 각 항목은 자체로도 하나의 값이지만, 항목의 원시 값(특정 타입으로 지정된 값)도 가질 수 있음
  • 열거형 이름 오른쪽에 타입을 명시해주면 됨
  • 원시 값을 사용하고 싶다면 rawValue 프로퍼티를 통해 값을 가져올 수 있음
// 열거형의 원시 값 지정과 사용 
enum School: String {
    case primary = "유치원"
    case elementry = "초등학교"
    case middle = "중학교"
    case high = "고등학교"
    case university = "대학교"
}

print(School.elementry.rawValue) // 초등학교
print(School.primary.rawValue) // 유치원 

var highestEducationLevel: School = .university
print("\(highestEducationLevel.rawValue)를 졸업했습니다.") // 대학교를 졸업했습니다.




enum weekDays: Character {
    case mon = "월", tue = "화", wed = "수", thu = "목", fri = "금", sat = "토", sun = "일"
}

let today:weekDays = weekDays.thu
print("오늘은 \(today.rawValue)요일 입니다.") // 오늘은 목요일 입니다.

 

  • 일부 항목에만 원시 값을 주는 것도 가능
  • 문자열의 형식의 원시 값을 지정한다면 각 항목 이름을 그대로 원시 값으로 갖게 됨
  • 정수 타입이라면 첫 항목을 기준으로 0부터 1씩 늘어난 값을 갖게 됨 
  • 열거형의 원시 값 정보를 안다면 원시 값을 통해 열거형 변수, 상수를 생성해줄 수 있음
  • 올바르지 않은 원시 값을 통해 생성 시 nil 반환 (실패 가능한 이니셜 라이저 (책 11.1.7) 기능)
// 열거형의 원시 값 일부 지정 및 자동 처리
enum School: String {
    case primary
    case elementry = "초등학교"
    case middle = "중학교"
    case high = "고등학교"
    case university
}

var highestEducationLevel: School = .university
print("\(highestEducationLevel.rawValue)를 졸업했습니다.") //university를 졸업했습니다.

// 원시 값을 통한 열거형 초기화 
let middle = School(rawValue: "중학교")
print(middle!) // middle

let university = School(rawValue: "대학교")
print(university) // nil

////

enum Numbers: Int {
    case zero
    case one
    case two
    case ten = 10
}

print("\(Numbers.zero.rawValue), \(Numbers.one.rawValue), \(Numbers.two.rawValue), \(Numbers.ten.rawValue)") // 0, 1, 2, 10

// 원시 값을 통한 열거형 초기화
let one = Numbers(rawValue: 1)
print(one!) // one

let three = Numbers(rawValue: 3)
print(three) // nil

 

3. 연관 값

  • 열거형의 각 항목이 연관 값을 가지게 되면, 기존 프로그래밍 언어의 공용체 형태를 띨 수도 있다.
  • 열거형 내의 항목(case)이 자신과 연관된 값을 가질 수 있다.
  • 연관 값은 각 항목 옆에 소괄호로 묶어 표현
  • 다른 항목이 연관 값을 갖는다고 모든 항목이 연관 값을 가질 필요는 없다.
  • 연관값을 확인하고 코드를 실행할 땐 주로 switch 문을 사용한다.

 

enum pizzaDough {
    case cheese, thin, origianl
}

enum pizzaTopping {
    case pepperoni, cheese, bacon
}

enum MainDish {
    case pasta(sauce: String, topping: String)
    case chicken(sauce: Bool)
    case rice
    case pizza(dough: pizzaDough, topping: pizzaTopping) // 피자의 도우, 토핑 등을 특정 메뉴로 한정 지을려면, 연관값을 열거형으로
}

var dinner = MainDish.pasta(sauce: "tomato", topping: "bacon")
dinner = .chicken(sauce: true)
dinner = .rice
dinner = .pizza(dough: pizzaDough.cheese, topping: pizzaTopping.pepperoni)


// switch를 이용한 연관값의 다양한 표현 
switch dinner {
case .chicken(sauce: true):
    print("1. Today's dinner menu is chicken with sauce")
case .rice:
    print("2. Today's dinner menu is rice")
case .pasta(sauce: "tomato", topping: "bacon"):
    print("3. Today's dinner menu is cream bacon pasta")
case .pasta(sauce: "tomato", _): // 와일드카드 패턴 사용 가능
    print("4. Today's dinner menu is tomato pasta")
case .pasta: // 연관값 생략 가능
    print("5. Today's dinner menu is pasta")
case .pizza(let dough, let topping): // 블록 내부에서 연관값을 사용할 땐 상수로 바인딩, 값을 변경할 때는 var 로 변경 가능
    print("6. Today's dinner menu is \(dough) \(topping) pizza")
case let .pizza(dough, topping):     // 모든 연관값을 동일한 형태로 바인딩한다면, let 키워드를 열거형 케이스 앞에 표기하는 것도 가능
    print("7. Today's dinner menu is \(dough) \(topping) pizza")
default:
    print("8. default")
    break
}

 

4. 항목 순회

  • 열거형에 포함된 모든 케이스를 알아야 할 때 사용
  • 열거형의 이름 뒤에 콜론(:)을 작성하고 한 칸 띄운 뒤 CaseIterable 프로토콜을 채택한다.
  • 그러면 열거형에 allCases라는 이름의 타입 프로퍼티를 통해 모든 케이스의 컬렉션을 생성해준다.
  • 원시 값을 갖는 열거형일 경우, 원시 값의 타입 다음에 쉼표(,)를 쓰고 띄어쓰기를 한 후 CaseIterable 프로토콜을 채택하면 된다.

 

// CaseIterable 프로토콜을 활용한 열거형의 항목 순회
enum School: CaseIterable {
    case elementry
    case middle
    case high
    case university
}

let allCases: [School] = School.allCases
print(allCases) //[School.elementry, School.middle, School.high, School.university]

// 원시값을 갖는 열거형의 항목 순회
enum School2: String, CaseIterable {
    case elementry = "초등학교"
    case middle = "중학교"
    case high = "고등학교"
    case university = "대학교"
}

let allCases2: [School2] = School2.allCases
print(allCases2) //[School.elementry, School.middle, School.high, School.university]

 

* 복잡한 열거형에서의 CaseIterable 프로토콜 사용에 대한 자세한 내용은 더보기를 클릭해주세요!

더보기

 

단순한 열거형에는 CaseIterable 프로토콜을 채택해 주는 것만으로 allCases 프로퍼티를 사용할 수 있지만

복잡해지는 열거형은 그렇지 않을 수도 있다.

 

예를 들어 첫 번째는 플랫폼별로 사용 조건을 추가하는 경우이다.

available 속성을 통해 특정 케이스를 플랫폼에 따라 사용할 수 있거나 없는 경우가 생긴다면 직접 allCases 프로퍼티를 구현해주어야 한다.

 

// available 속성을 갖는 열거형의 항목 순회
enum School: String, CaseIterable {
    case elementry = "초등학교"
    case middle = "중학교"
    case high = "고등학교"
    case university = "대학교"
    @available(iOS, obsoleted: 12.0)
    case graduate = "대학원"
    
    static var allCases: [School] {
        let all: [School] = [.elementry,
                             .middle,
                             .high,
                             .university]
        
        #if os(iOS)
        return all
        #else
        return all + [.graduate]
        #endif
    }
}

let allCases: [School] = School.allCases
print(allCases) // 실행 환경에 따라 다른 결과 출력

 

두 번째로는 열거형의 케이스가 연관 값을 갖는 경우이다.

map, reduce 등의 메서드는 15장에 나올 예정 

 

// 연관 값을 갖는 열거형의 항목 순회
enum PastaTaste: CaseIterable {
    case tomato, cream
}

enum PizzaDough: CaseIterable {
    case cheese, thin, original
}

enum PizzaTopping: CaseIterable {
    case pepperoni, cheese, bacon
}

enum MainDish: CaseIterable {
    case pasta(taste: PastaTaste)
    case pizza(dough: PizzaDough, topping: PizzaTopping)
    case chicken(sauce: Bool)
    case rice
    
    static var allCases: [MainDish] {
        return PastaTaste.allCases.map(MainDish.pasta)
            + PizzaDough.allCases.reduce([]) { (result, dough) -> [MainDish] in
                result + PizzaTopping.allCases.map { (topping) -> MainDish in
                    MainDish.pizza(dough: dough, topping: topping)
                }
            }
            + [true, false].map(MainDish.chicken)
            + [MainDish.rice]
    }
}

print(MainDish.allCases.count) // 14
print(MainDish.allCases) // 모든 경우의 연관 값을 갖는 케이스 컬렉션

 

 

5. 순환 열거형

  • 열거형 항목의 연관 값이 열거형 자신의 값이고자 할 때 사용
  • indirect 키워드 사용
  • 특정 항목에만 한정하고 싶다면 case 키워드 앞에 indirect, 열거형 전체에 적용하고 싶다면 enum 키워드 앞에 indirect 키워드를 붙임

 

// 특정 항목에 순환 열거형 항목 명시
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// 열거형 전체에 순환 열거형 명시
indirect enum ArithmeticExpression2 {
    case number(Int)
    case addition(ArithmeticExpression2, ArithmeticExpression2)
    case multiplication(ArithmeticExpression2, ArithmeticExpression2)
}

 

 

열거형에는 정수를 연관 값으로 갖는 number라는 항목이 있고,

덧셈을 위한 addition 항목

곱셈을 위한 multiplication 항목이 있다.

 

아래는 ArithmeticExpression 열거형을 사용해 (5 + 4) * 2 연산을 구하는 예제이다.

evaluate는 ArithmeticExpression 열거형의 계산을 도와주는 순환 함수이다.

 

// 순환 열거형 사용
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let final = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
        case .number(let value):
            return value
        case .addition(let left, let right):
            return evaluate(left) + evaluate(right)
        case .multiplication(let left, let right):
            return evaluate(left) * evaluate(right)
    }
}

let result: Int = evaluate(final)
print("(5 + 4) * 2 = \(result)") // 18

 

6. 비교 가능한 열거형 

Comparable 프로토콜을 준수하는 연관 값만 갖거나 연관 값이 없는 열거형은 Comparable 프로토콜을 채택하면 각 케이스를 비교할 수 있다.

앞에 위치한 케이스가 더 작은 값이 된다.

 

//비교 가능한 열거형의 사용
enum Condition: Comparable {
    case terrible
    case bad
    case good
    case great
}

let myCondition: Condition = Condition.great
let yourCondition: Condition = Condition.bad

if(myCondition >= yourCondition) {
    print("I'm in better condition")
} else {
    print("You are in better condition")
}

////

enum Device: Comparable {
    case iPhone(version: String)
    case iPad(version: String)
    case macBock
    case iMac
}

var devices: [Device] = []
devices.append(Device.iMac)
devices.append(Device.iPhone(version: "14.3"))
devices.append(Device.iPhone(version: "6.1"))
devices.append(Device.iPad(version: "10.3"))
devices.append(Device.macBock)

let sortedDevices: [Device] = devices.sorted()
print(sortedDevices) // Device.iPhone(version: "14.3"), Device.iPhone(version: "6.1"), Device.iPad(version: "10.3"), Device.macBock, Device.iMac]

 

참고

아래를 참고해 정리한 내용입니다.

 

스위프트 프로그래밍 3판(야곰) - 한빛미디어
반응형