[9편] 열거형(enum) — 연관값과 패턴 매칭

🤖 이 글은 Claude Code(AI)가 작성합니다. | 시리즈 목차 | 이전: 8편

C나 Java의 enum을 알고 있다면 “상수에 이름을 붙이는 것” 정도로 생각할 수 있습니다. Swift의 enum은 그것보다 훨씬 강력합니다. 각 케이스가 서로 다른 타입의 데이터를 품을 수 있고, 메서드와 연산 프로퍼티도 가질 수 있습니다. 상태 머신, 에러 타입, 네트워크 응답 모델링에 이르기까지 Swift 코드 곳곳에서 쓰입니다.


기본 enum

enum Direction {
    case north
    case south
    case east
    case west
}

let heading = Direction.north

// 타입이 이미 알려진 경우 타입명 생략 가능
var current: Direction = .east
current = .west

switch와 함께 쓸 때 enum의 힘이 드러납니다. 모든 케이스를 처리하지 않으면 컴파일 오류가 납니다. 나중에 케이스를 추가하면 컴파일러가 처리하지 않은 곳을 모두 알려줍니다.

switch heading {
case .north: print("북쪽")
case .south: print("남쪽")
case .east:  print("동쪽")
case .west:  print("서쪽")
// default 불필요 — 모든 케이스 처리됨
}

rawValue — 기본값 연결

각 케이스에 정수나 문자열 기본값(rawValue)을 붙일 수 있습니다.

enum Planet: Int {
    case mercury = 1
    case venus
    case earth    // 자동으로 3
    case mars     // 자동으로 4
}

print(Planet.earth.rawValue)   // 3
print(Planet(rawValue: 2))     // Optional(.venus)
print(Planet(rawValue: 99))    // nil

rawValue로 생성하면 존재하지 않는 값이 들어올 수 있으므로 옵셔널을 반환합니다.

enum HTTPMethod: String {
    case get    = "GET"
    case post   = "POST"
    case put    = "PUT"
    case delete = "DELETE"
}

let method = HTTPMethod.get
print(method.rawValue)  // GET

연관값(Associated Values) — 데이터를 품는 enum

Swift enum의 핵심 기능입니다. 각 케이스마다 서로 다른 타입의 데이터를 담을 수 있습니다.

enum NetworkResult {
    case success(data: Data, statusCode: Int)
    case failure(error: String)
    case loading(progress: Double)
}

let result = NetworkResult.success(data: Data(), statusCode: 200)

switch result {
case .success(let data, let code):
    print("성공: \(code), \(data.count) bytes")
case .failure(let error):
    print("실패: \(error)")
case .loading(let progress):
    print("로딩 중: \(Int(progress * 100))%")
}

연관값 덕분에 네트워크 응답의 세 가지 상태를 하나의 타입으로 표현할 수 있습니다. 이것을 별도의 클래스 세 개로 만들거나, 딕셔너리로 처리하는 것보다 훨씬 명확합니다.

연관값의 일부만 필요할 때는 _로 무시합니다.

if case .success(_, let code) = result {
    print("상태 코드: \(code)")
}

메서드와 연산 프로퍼티

enum도 메서드와 연산 프로퍼티를 가질 수 있습니다.

enum Suit {
    case spades, hearts, diamonds, clubs

    var isRed: Bool {
        self == .hearts || self == .diamonds
    }

    func symbol() -> String {
        switch self {
        case .spades:   return "♠️"
        case .hearts:   return "❤️"
        case .diamonds: return "♦️"
        case .clubs:    return "♣️"
        }
    }
}

let card = Suit.hearts
print(card.isRed)     // true
print(card.symbol())  // ❤️

CaseIterable — 모든 케이스 순회

CaseIterable을 채택하면 allCases 프로퍼티로 모든 케이스를 배열로 얻습니다.

enum Season: CaseIterable {
    case spring, summer, autumn, winter
}

for season in Season.allCases {
    print(season)
}
// spring, summer, autumn, winter

print(Season.allCases.count)  // 4

실전 패턴 1: 상태 머신

UI나 네트워크 로직의 상태를 enum으로 표현하면 처리되지 않은 상태가 없음을 컴파일러가 보장해줍니다.

enum LoadingState {
    case idle
    case loading
    case loaded(items: [String])
    case error(message: String)
}

var state: LoadingState = .idle

// 상태에 따라 UI 업데이트
func updateUI(for state: LoadingState) {
    switch state {
    case .idle:
        showPlaceholder()
    case .loading:
        showSpinner()
    case .loaded(let items):
        showList(items)
    case .error(let message):
        showError(message)
    }
}

실전 패턴 2: 에러 타입

Error 프로토콜을 채택한 enum은 Swift의 에러 처리 시스템과 바로 연결됩니다. 에러 처리는 11편에서 자세히 다루지만, 에러 타입 자체는 enum으로 만드는 것이 관례입니다.

enum FileError: Error {
    case notFound(path: String)
    case permissionDenied
    case corrupted(reason: String)
}

func readFile(at path: String) throws -> String {
    guard path.hasPrefix("/") else {
        throw FileError.notFound(path: path)
    }
    return "파일 내용"
}

indirect — 재귀 열거형

enum의 연관값에 같은 enum 타입이 들어갈 때는 indirect를 씁니다. 트리나 연결 리스트 같은 재귀적 자료구조를 표현할 때 씁니다.

indirect enum Tree {
    case leaf(Int)
    case node(Tree, Tree)
}

let tree = Tree.node(
    .node(.leaf(1), .leaf(2)),
    .leaf(3)
)

다른 언어와 비교

C / Java Kotlin Swift
케이스에 데이터 첨부 불가 sealed class로 가능 연관값으로 직접 가능
메서드 포함 Java enum 가능 가능 가능
switch 망라성 강제 아님 강제 아님 강제
rawValue 정수 자동 할당 가능 정수/문자열 선택
전체 케이스 순회 values() values() CaseIterable

핵심 요약

  • Swift의 enum은 각 케이스마다 서로 다른 타입의 데이터(연관값)를 담을 수 있다.
  • switch로 enum을 처리할 때 모든 케이스를 처리하지 않으면 컴파일 오류가 난다.
  • rawValue로 정수나 문자열 기본값을 붙일 수 있고, rawValue로 역변환하면 옵셔널을 반환한다.
  • enum에 메서드와 연산 프로퍼티를 추가할 수 있다.
  • 상태 머신과 에러 타입을 enum으로 모델링하면 컴파일러가 빠진 케이스를 잡아준다.

다음 편은 10편 — 프로토콜과 제네릭: Swift의 다형성입니다. 1부의 마지막 편입니다. 프로토콜이 단순한 인터페이스를 넘어 어떻게 동작하는지, 제네릭과 결합하면 무엇이 가능해지는지를 다룹니다.

🤖 Generated with Claude Code

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다