Swift 5. 열거형

 Swift 5. 열거형


- 열거형

연관된 항목들을 묶어서 표현할 수 있는 타입

프로그래머가 정의해준 항목 값 외에는 추가나 수정이 불가능

-> 정해진 값만 열거형 값에 속할 수 있음


* 열거형이 요긴하게 사용되는 경우

제한된 선택지를 주고 싶을 때

정해진 값 외에는 입력받고 싶지 않을 때

예상된 입력값이 한정되어 있을 때


* 열거형으로 묶을 수 있는 항목 예시

무선통신 방식 : WiFi, 블루투스, LTE, 3G 등

학생 : 초등학생, 중학생, 고등학생, 대학생, 대학원생 등

지역 : 서울 경기도, 강원도, 경상도, 전라도, 충청도, 제주도 등



Swift 의 열거형은 값을 가질 수도 있고 안 가질 수도 있음

각 항목이 그 자체로 고유의 값이 될 수도 있음 

원시 값(Raw Value)이라는 형태로 실제 값(정수, 실수, 문자 타입 등)을 가질 수도 있음

연관값을 사용해서 각 항목의 값의 묶음을 구현할 수도 있음

각 열거형이 고유의 타입으로 인정되기 때문에 실수로 버그가 일어날 가능성을 원천 봉쇄

switch 구문과 함께 활용할 수도 있음






1. 기본 열거형

enum 이라는 키워드로 선언

enum [열거형 이름] {

    case [항목 이름] ...

}


* 예시

enum School {

    case elementary // 초등학교

    case middle     // 중학교

    case high       // 고등학교

    case college    // 단과 대학

    case university // 종합 대학

}

또는 각 항목 들을 한 줄로 표현

enum School {

    case elementary, middle, high, college, university

}




- 열거형 변수(상수) 생성

var (or let) [변수 이름]: [열거형 이름] = [열거형 이름].[항목 이름]

                                            or

var (or let) [변수 이름]: [열거형 이름] = .[항목 이름]    // 뒤의 열거형 이름 생략 가능


* 열거형 변수의 값은 같은 타입의 열거형의 내부 항목으로만 변경 가능



* 예시

enum School {

    case elementary, middle, high, college, university

}


var student1: School = School.elementary

var student2: School = .middle


student2 = .high // 같은 타입의 열거형의 내부 항목으로만 값 변경 가능


print(student1)

<실행결과>

elementary

high

(열거형의 타입 이름을 값으로 가짐)






2. 원시값 (Raw Value)

열거형의 각 항목은 그 자체로도 하나의 값이지만 항목의 원시값(Raw Value)도 가질 수 있음

-> 특정 타입으로 지정된 값을 가질 수 있음

열거형을 선언할 때 열거형의 이름 오른쪽에 타입을 명시

.rawValue 프로퍼티를 통해 원시값을 가져올 수 있음



* 예시

enum Days: String{  // 원시값의 타입 명시 (String)

    // 각 항목의 원시값 지정

    case mon = "월요일"

    case tue = "화요일"

    case wed = "수요일"

    case thu = "목요일"

    case fri = "금요일"

    case sat = "토요일"

    case sun = "일요일"

}


let today: Days = .mon


print("오늘은 \(today.rawValue) 입니다") // .rawValue 프로퍼티를 통해 원시값을 가져옴


<실행결과>

오늘은 월요일 입니다



- 일부 항목만 원시값 지정

일부 항목만 원시값 지정 가능

나머지 항목은 Swift가 알아서 처리

문자열 형식 : 각 항목의 이름을 그대로 원시값으로 사용

정수 타입 : 첫 항목을 기준으로 0부터 1씩 늘어남 (이전 항목에 원시값이 지정되어 있으면 이전 항목의 원시값 + 1씩 해서 원시값으로 지정)



* 예시

enum Days: String{  // String 타입의 원시값

    case mon = "월요일"

    case tue = "화요일"

    case wed = "수요일"

    case thu = "목요일"

    case fri = "금요일"

    // Swift 에서 항목의 이름을 원시값으로 지정

    case sat    // 원시값 : sat

    case sun    // 원시값 : sun

}


let today: Days = .sun


print("오늘은 \(today.rawValue) 입니다")



enum Nums: Int{ // Int 타입의 원시값

    case one = 1

    case two = 10

    case three // 이전 항목의 원시값 +1

}


var n: Nums = .three

print(n.rawValue)

<실행결과>

오늘은 sun 입니다

11




- 원시값 정보를 통해 열거형 변수 or 상수 생성

var(let) [변수(상수) 이름] = [열거형 이름](rawValue: [원시값])


올바르지 않은 원시값을 통해 생성하려고 하면 nil 반환(실패 가능한 이니셜라이저)



* 예시 

let today = Days(rawValue: "화요일") // 문자열 원시값을 이용한 상수 생성

print(today)


enum Nums: Int{ // Int 타입의 원시값

    case one = 1

    case two = 2

    case three = 3

}


var n = Nums(rawValue: 2)   // 정수형 원시값을 이용한 변수 생성

print(n)

n = Nums(rawValue: 4)       // 없는 원시값을 이용하면 nil

print(n)

<실행결과>

Optional(__lldb_expr_18.Days.tue)

Optional(__lldb_expr_18.Nums.two)

nil






3. 연관값

열거형의 항목이 자신과 연관된 값을 가질 수 있음

연관된 값은 소괄호 ( ) 안에 연관값 이름과 타입을 묶어서 표현

연관값의 이름을 지정하지 않고 타입만 지정해 줘도 됨

모든 항목이 연관값을 가질 필요는 없음



* 예시

enum MainDish {

    case pasta(taste: String)

    case pizza(dough: String, topping: String)

    case chicken(withSauce: Bool)

    case rice

}


var dinner: MainDish = .pasta(taste: "크림")

dinner = .pizza(dough: "치즈크러스트", topping: "치즈")

dinner = .chicken(withSauce: true)

dinner = .rice




연관값을 한정된 값으로 받으려면 한정된 값 들로 또다른 열거형을 만들어서 연관값의 자료형에 해당 열거형의 이름 작성



* 예시

// 연관값을 열거형으로 선언

enum PastaTaste{

    case cream, tomato

}


enum PizzaDough {

    case cheeseCrust

    case original

}


enum PizzaTopping{

    case pepperoni

    case cheese

    case bacon

}


enum MainDish {

    case pasta(taste: PastaTaste)   // 연관값의 자료형을 미리 선언한 열거형으로 지정

    case pizza(dough: PizzaDough, topping: PizzaTopping)

    case chicken(withSauce: Bool)

    case rice

}



var dinner: MainDish = .pasta(taste: PastaTaste.tomato)

dinner = .pizza(dough: PizzaDough.cheeseCrust, topping: PizzaTopping.bacon)






4. 항목 순회

열거형에 포함된 모든 항목들을 알아야 할 때

=> 열거형을 선언할 때 열거형 이름 뒤에 : CaseIterable 작성 (CaseIterable 프로토콜 채택)

-> 열거형의 .allCases 타입 프로퍼티를 통해 모든 항목의 컬렉션(배열)을 생성



* 예시 

enum Days: CaseIterable{    // CaseIterable 프로토콜 채택

    case mon

    case tue

    case wed

    case thu

    case fri

    case sat

    case sun

}


// .allCases 타입 프로퍼티를 통해 열거형의 모든 항복을 배열로 선언

let allCases: [Days] = Days.allCases

print(allCases)

<실행결과>

[__lldb_expr_4.Days.mon, __lldb_expr_4.Days.tue, __lldb_expr_4.Days.wed, __lldb_expr_4.Days.thu, __lldb_expr_4.Days.fri, __lldb_expr_4.Days.sat, __lldb_expr_4.Days.sun]



원시값을 갖는 열거형의 경우 원시값의 타입 다음에 쉼표( , )를 쓰고 띄어쓰기를 한 후 CaseIterable 프로토콜 채택



* 예시

enum Days: String, CaseIterable{ // 원시값의 타입 다음에 CaseIterable 프로토콜 채택

    case mon = "월요일"

    case tue = "화요일"

    case wed = "수요일"

    case thu = "목요일"

    case fri = "금요일"

    case sat = "토요일"

    case sun = "일요일"

}




* 단순한 열겨형의 경우에는 CaseIterable 프로토콜을 채택해 주는 껏만으로 .allCases 프로퍼티를 사용할 수 있지만 복잡해지는 열거형은 직접 .allCases 프로토콜을 구현해 줘야 하는 경우도 있음

예) 플랫폼 별로 사용 조건을 추가하는 경우, 열거형의 항목이 연관값을 갖는 경우



* 예시 - 실행 환경에 때라 결과가 다른 경우

enum School: String, CaseIterable{

    case elementary = "초등학교"

    case middle = "중학교"

    case high = "고등학교"

    case college = "단과대학"

    @available (iOS, obsoleted: 12.0)

    case university = "종합대학"

    

    // .allCases 프로퍼티 구현

    static var allCases: [School]{

        let all: [School] = [.elementary,

                             .middle,

                             .high,

                             .college]

        #if os(iOS)

        return all

        #else

        return all + [.univarsity]

        #endif

    }

}


let allCases: [School] = School.allCases

print(allCases)

<실행결과>

(iOS 인 경우)

[__lldb_expr_11.School.elementary, __lldb_expr_11.School.middle, __lldb_expr_11.School.high, __lldb_expr_11.School.college]




* 예시 - 열거형의 항목들이 연관값을 갖는 경우

// 연관값을 열거형으로 선언

enum PastaTaste: CaseIterable{

    case cream, tomato

}


enum PizzaDough: CaseIterable {

    case cheeseCrust, original

}


enum PizzaTopping: CaseIterable{

    case pepperoni, cheese, bacon

}


enum MainDish: CaseIterable {

    case pasta(taste: PastaTaste)   // 연관값의 자료형을 미리 선언한 열거형으로 지정

    case pizza(dough: PizzaDough, topping: PizzaTopping)

    case chicken(withSauce: Bool)

    case rice

    

    // .allCases 프로퍼티를 직접 구현 (모든 경우의 연관값을 반환)

    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)

print(MainDish.allCases)

<실행결과>

11

[__lldb_expr_13.MainDish.pasta(taste: __lldb_expr_13.PastaTaste.cream), __lldb_expr_13.MainDish.pasta(taste: __lldb_expr_13.PastaTaste.tomato), __lldb_expr_13.MainDish.pizza(dough: __lldb_expr_13.PizzaDough.cheeseCrust, topping: __lldb_expr_13.PizzaTopping.pepperoni), __lldb_expr_13.MainDish.pizza(dough: __lldb_expr_13.PizzaDough.cheeseCrust, topping: __lldb_expr_13.PizzaTopping.cheese), __lldb_expr_13.MainDish.pizza(dough: __lldb_expr_13.PizzaDough.cheeseCrust, topping: __lldb_expr_13.PizzaTopping.bacon), __lldb_expr_13.MainDish.pizza(dough: __lldb_expr_13.PizzaDough.original, topping: __lldb_expr_13.PizzaTopping.pepperoni), __lldb_expr_13.MainDish.pizza(dough: __lldb_expr_13.PizzaDough.original, topping: __lldb_expr_13.PizzaTopping.cheese), __lldb_expr_13.MainDish.pizza(dough: __lldb_expr_13.PizzaDough.original, topping: __lldb_expr_13.PizzaTopping.bacon), __lldb_expr_13.MainDish.chicken(withSauce: true), __lldb_expr_13.MainDish.chicken(withSauce: false), __lldb_expr_13.MainDish.rice]

(모든 경우의 연관값을 갖는 항목들의 배열)






5. 순환(재귀)  열거형

열거형의 연관값이 자기 자신의 값인 경우

indirect 키워드 사용

특정 항목에만 한정 하려면 case 키워드 앞에 indirect 키워드 작성

열거형 전체에 적용 하려면 enum 키워드 앞에 indirect 키워드 작성



* 예시 - 특정 항목에 순환 열거형 항목 명시

enum Math {

    case number(Int)

    indirect case addition(Math, Math)

    indirect case multiplication(Math, Math)

}


* 예시 - 열거형 전체에 순환 열거형 명시

indirect enum Math {

    case number(Int)

    case addition(Math, Math)

    case multiplication(Math, Math)

}




* 예시 - Math 열거형을 이용해서 (5 + 7) * 3 을 연산

indirect enum Math {

    case number(Int)

    case addition(Math, Math)

    case multiplication(Math, Math)

}


// 계산에 사용할 정수를 열거형의 number 항목의 연관값으로 받음

let five: Math = .number(5)

let seven: Math = .number(7)


// 덧셈 계산을 위해 열거형 Math의 상수인 five와 seven를 연관값으로 받음

let sum: Math = .addition(five, seven)


// 곱셈 계산을 위해 열거형 Math의 상수인 sum과 직접 Math.number를 연관값으로 넘겨줌

let final: Math = .multiplication(sum, Math.number(3))



// 열거형의 계산을 도와주는 순환(재귀)함수

func evaluate(_ expression: Math) -> 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 + 7) * 3 = \(result)")






6. 비교 가능한 열거형

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

앞에 위치한 항목이 더 작음



* 예시 - 연관값이 없는 경우

enum Condition: Comparable{ // Comparable 프로토콜 채택

    case terrible

    case bad

    case good

    case great

    // 아래로 내려갈 수록 커짐

}


let myCondition: Condition = .good

let yourCondition: Condition = .bad


// 열거형의 비교

if myCondition > yourCondition{

    print("내 상태가 더 좋다")

}else {

    print("네 상태가 더 좋다")

}

<실행결과>

내 상태가 더 좋다




* 예시 - 연관값이 있는 경우

enum Device: Comparable{

    case iPhon (version: String)

    case iPad (version: String)

    case macbook

    case iMac

}


var devices: [Device] = []

devices.append(Device.iMac)

devices.append(Device.iPhon(version: "13"))

devices.append(Device.iPad(version: "9"))

devices.append(Device.macbook)

devices.append(Device.iPhon(version: "12 mini"))


let sortedDevices = devices.sorted()

print(sortedDevices)


<실행결과>

[__lldb_expr_19.Device.iPhon(version: "12 mini"), __lldb_expr_19.Device.iPhon(version: "13"), __lldb_expr_19.Device.iPad(version: "9"), __lldb_expr_19.Device.macbook, __lldb_expr_19.Device.iMac]

-> 항목이 같은 경우에는 연관값으로 크기를 비교







댓글

이 블로그의 인기 게시물

Spring Boot 6. 계시판 프로젝트

Spring Boot 2. Maria 데이터베이스 & Spring Data JPA

Django 10. DB 연동