[Swift] Enumeration


Swift Enumeration


Enumeration 열거형

연관된 상수(케이스)들을 하나의 이름으로 묶은 자료형 (클래스)
케이스가 선택가능한 (한정된) 가지 수로 정해져 있을 때 정의

열거형의 기본 포맷

enum Weekday { // 타입 이름은 대문자로 시작
    case monday  // 각 케이스 이름은 소문자로 시작 
    case tuesday  // 이전 스위프트 버전은 대문자도 있음
    case wednesday
    case thursday
    case friday
    case saturday
    case sunday
}

열거형을 사용하면 미리 정의해둔 타입에 케이스에서 벗어날 수 없으므로 코드의 가독성과 안정성이 높아짐
(명확한 분기 처리 가능)

열거형 타입

var weekday: Weekday = Weekday.monday
var today: Weekday = .monday  // 앞에서 명시적으로 타입을 지정해 주고 있기 때문에 생략 가능

today = .tuesday  // 위에서 today의 타입을 지정해 주었기 때문에 아래에서도 생략 가능

열거형 분기 처리

// if문으로 열거형 분기처리
if today == .monday {
    print("월요일")
} else if today == .tuesday {
    print("화요일")
} else if today == .wednesday{
    print("수요일")
} else if today == .thursday{
    print("목요일")
} else if today == .friday{
    print("금요일")
} else if today == .saturday{
    print("토요일")
} else {
    print("일요일")
}

//switch문으로 분기처리 (주로 switch문을 사용)
switch today {
case .monday:
    print("월요일")
case .tuesday:
    print("화요일")
case .wednesday:
    print("수요일")
case .thursday:
    print("목요일")
case .friday:
    print("금요일")
case .saturday:
    print("토요일")
case .sunday:
    print("일요일")
}

열거형의 원시값 (Raw Values)

열거형의 원시값은 매칭되는 기본값(정수, 문자열등 Hashable한 타입이면 모두 가능)을 정해, 열거형을 좀 더 쉽게 활용 가능

enum Alignment1: Int {
    case left
    case center
    case right
}
// 원시값을 입력하지 않으면 0부터 정수값이 하나씩 증가하며 자동으로 저장됨

enum Alignment2: String {
    case left = "L"
    case center = "C"
    case right = "R"
}
// 가능하긴 하지만 문자열 방식으로는 잘 사용하진 않음

let align1 = Alignment1(rawValue: 0)  // left
let alignment1 = Alignment1.center.rawValue // 1
let align2 = Alignment2(rawValue: : "C")  // center
let alignment2 = Alignment2.center.rawValue  // "C"
// 인스턴스 생성 시 - 옵셔널 타입으로 리턴

열거형의 원시값 활용

숫자 또는 문자열과 매칭시켜 자유롭게 활용 가능

enum RpsGame: Int {
    case rock
    case paper
    case scissors
}

let number = Int.random(in: 0...2)

if let r = RpsGame(rawValue: number) {
    print(r)  // 0: rock / 1: paper / 2: scissors
}
// 옵셔널 타입으로 리턴하기 때문에 옵셔널 바인딩으로 벗겨내 줘야함

열거형의 연관값 (Associated Values)

열거형의 연관값은 구체적인 추가정보를 저장하기 위해 사용

  • 각 케이스별로 상이한 특징이 있고, 그것을 저장, 활용할 필요가 있을 때
  • 개별케이스마다 저장할 형식을 따로 정의 (자료형에 제한이 없음, 튜플의 형태)
  • 하나의 케이스에 서로 다른 연관값을 저장할 수 있음
    • 선언시점이 아니라, 새로운 열거형 값을 생성할때 저장
enum Computer {
    case cpu(core: Int, ghz: Double)
    case ram(Int, String)
    case hardDisk(gb: Int)
}

let myChip1 = Computer.cpu(core: 8, ghz: 3.5)
let myChip2 = Computer.cpu(core: 4, ghz: 2.0)

let myChip3 = Computer.ram(16, "DRAM")
let myChip4 = Computer.ram(4, "SRAM")
let myChip5 = Computer.ram(32, "DRAM")

let myChip6 = Computer.hardDisk(gb: 128)
let myChip7 = Computer.hardDisk(gb: 512)

// 연관값을 가진 케이스를 패턴 매칭시키기
switch chip {
case let .cpu(a, b):
    print("CPU \(a)코어 \(b)GHz입니다.")
case let .ram(a, _):
    print("램 \(a)기가램 입니다.")
case let .hardDisk(a) :
    print("하드디스크 \(a)기가 용량입니다.")
}

원시값(Raw Values)과 연관값(Associated Values)의 차이

 원시값연관값
사용목적열거형 타입의 각 케이스에 정수 또는 문자열을 매칭시켜, 타입을 생성하거나 다룰때 조금 더 편하게 사용열거형 타입의 각 케이스의 카테고리에는 해당하지만, 보다 구체적인 정보를 저장해서 사용하려고 할때
선언 방법선언 타입이 제한 </br>Int, String 주로 사용</br>원칙적으로는 Hashable 프로토콜을 준수하는 타입 모두 가능선언 타입에 제한없이 자유롭게 정의 가능
연관된 정보를 추가적으로 저장하는 개념이기 때문에
값의 저장열거형의 선언 시점
선언시에 case마다 값이 매칭
인스턴스 생성시
값의 변경선언 시에 정의 개념이기에 불가능하나의 케이스에 서로 다른 값들을 저장 가능

주의) 하나의 열거형에서 원시값과 연관값을 동시에 사용하는 것은 불가능

Optional 타입의 정확한 이해

옵셔널 타입은 내부에서 열거형으로 이루어져 있음

enum Optional<Wrapped> {     // 제네릭 문법
    case some(Wrapped)
    case none  // .none은 nil과 완전히 동일
}

옵셔널 열거형 패턴 (Enumeration Case Pattern)

열거형 안에 옵셔널이 존재할 때

enum SomeEnum {
    case left
    case right
}

let x: SomeEnum? = .left

// 원칙
switch x {
case .some(let value):      // Optional.some(let value) = Optional.some(SomeEnum.left)
    switch value {
    case .left:
        print("왼쪽으로 돌기")
    case .right:
        print("오른쪽으로 돌기")
    }
case .none:
    print("계속 전진")
}

// 편의적 기능 제공
switch x {
case .some(.left):
    print("왼쪽으로 돌기")
case .some(.right):
    print("오른쪽으로 돌기")
case .none:
    print("계속 전진")
}

열거형은 한정된 사례로 만든 타입이고, 스위치문은 표현식에 대한 분기처리에 최적화 되어 있기 때문에 활용하기 적합하다.
(switch문은 옵셔널을 쉽게 사용이 가능하게 편의적 기능도 제공함)

열거형에 연관값이 있는 경우

특정 케이스만 뽑아서 다양하게 활용이 가능함

// 값 하나를 사용하기 위해 switch문 전체를 다 써야하는 불편
switch chip {
case Computer.hardDisk(gb: let gB):      // let gB = 연관값
    print("\(gB)기가 바이트 하드디스크")
default:
    break
}

// if case를 통해 간편하게 표현 가능
if case Computer.hardDisk(gb: let gB) = chip {
    print("\(gB)기가 바이트 하드디스크")
}
// 조건을 줘서 처리를 다양하게 활용 가능
if case Computer.hardDisk(gb: let gB) = chip, gB == 256 {
    print("256기가 바이트 하드디스크")
}

// 다양한 chip을 원소로 하는 chiplists라는 배열 생성
let chiplists: [Computer] = [
    .cpu(core: 4, ghz: 3.0),
    .cpu(core: 8, ghz: 3.5),
    .ram(16, "SRAM"),
    .ram(32, "DRAM"),
    .cpu(core: 8, ghz: 3.5),
    .hardDisk(gb: 500),
    .hardDisk(gb: 256)
]

// for case를 통해 배열 중에서 특정 케이스만 뽑아서 활용 가능
for case let .cpu(core: c, ghz: h) in chiplists {
    print("CPU칩: \(c)코어, \(h)헤르츠")
}

옵셔널 패턴

열거형 케이스 패턴을 좀 더 간소화시킨 패턴

let a: Int? = 1

// 열거형 케이스 패턴
switch a {
case .some(let z):
    print(z)
case .none:  // nil이라고 써도됨
    print("nil")
}

// 옵셔널 패턴
switch a {
case let z?:      // .some을 조금 더 간소화하는 문법
    print(z)
case nil:         // .none 이라고 써도됨
    print("nil")
}

@unknown 키워드

만약 열거형이 Non-frozen 열거형이라서 케이스가 늘어나는 경우에 대한 안정성은 보장할 수 없음
에러는 나지 않을 수 있지만 로직이 옳다고는 할 수 없음

@unknown 키워드를 default블럭에 추가 한다면

switch문에서 열거형의 모든 케이스를 다루지 않는 경우, 스위치문에서 모든 열거형의 케이스를 다루지 않았다고 경고를 통해 알려줌 -> 개발자의 실수 가능성을 컴파일 시점에 알려줌

  • “Switch must be exhatstive”로 경고 표시
enum LoginProvider: String {      // 3가지 케이스 -> 4가지 케이스
    case email
    case facebook
    case google
    case kakaotalk  // 추가된 케이스
}

let userLogin = LoginProvider.email

switch userLogin {
case .email:
    print("이메일 로그인")
case .facebook:
    print("페이스북 로그인")
case .google:
    print("구글 로그인")  // 구글에 대한 케이스가 사라진다면 경고를 통해 알려줌
@unknown default:
    print("그 외의 모든 경우")
}

참고

Ellen Swift Class






© 2021. by hminkim