TIL: 열거형(Enumeration, Enum)

Royce·2025년 3월 17일

Swift 문법

목록 보기
21/63

기본 타입(Basic Type)

  • 기본 타입은 Swift에서 가장 기본적인 데이터 타입이다. 우리가 흔히 사용하는 자료형이 여기에 포함된다
타입설명예시
String문자열let name: String = "Royce"
Charactor문자let letter: Charactor = "A"
Int정수let number: Int = 10
Double실수let pi: Double = 3.14
Bool참 / 거짓let isRaining: Bool = true
  • 하지만, 기본 타입만으로는 현실 세계의 복잡한 개념을 표현하기 어려울 때가 있다
  • 예를 들어, “요일”을 표현한다고 할 때 String으로 "Monday", "Tuesday" 같은 값을 저장할 수 있지만, 오타가 나거나 잘못된 값이 들어갈 수도 있다
let today: String = "Mondey" // ❌ 오타 발생!

사용자 정의 타입(Custom Type)

  • 기본 타입만으로 해결하기 어려운 문제를 해결하기 위해, 사용자 정의 타입을 만들 수 있다
타입설명예시
struct (구조체)여러 개의 값을 하나로 묶을 때 사용struct Person { var name: String; var age: Int }
class (클래스)객체지향 프로그래밍에서 객체를 정의할 때 사용class Animal { var species: String }
enum (열거형)관련된 값들의 묶음을 정의할 때 사용enum Weekday { case monday, tuesday }

열거형(Enumeration, Enum)

  • 열거형은 특정한 값의 집합을 표현하는 사용자 정의 타입이다.

열거형을 사용하는 이유

  • 연관된 값들을 그룹화해서 실수를 줄일 수 있다
  • 코드의 가독성이 좋아진다
  • 잘못된 값을 방지할 수 있다
  • 자동 완성 기능을 활용할 수 있다

예를 들어, 요일을 표헌한다고 가정해보자. 열거형을 사용하면 아래처럼 명확하게 정의할 수 있다.

enum Weekday {
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
    case sunday
}

언제 열거형을 사용할까

제한된 선택지가 있을 때

  • 예제: 교통 신호등
  • 빨강, 노랑, 초록 이외의 값이 나올 가능성이 없는 경우 열거형을 사용하면 실수를 방지할 수 있다
enum TrafficLight {
    case red, yellow, green
}

func getSignalMessage(signal: TrafficLight) -> String {
    switch signal {
    case .red:
        return "정지하세요"
    case .yellow:
        return "조심하세요"
    case .green:
        return "출발하세요"
    }
}

print(getSignalMessage(signal: .red)) // "정지하세요"

서로 연관된 값들을 그룹화할 때

  • 예제: 앱에서 나타낼 알림 타입
  • success, error, warning 같은 의미적으로 그룹이 되는 값들을 정의할 때 유용하다
enum AlertType {
    case success
    case error
    case warning
}

func showAlert(type: AlertType) {
    switch type {
    case .success:
        print("성공 알림!")
    case .error:
        print("오류 발생!")
    case .warning:
        print("주의하세요!")
    }
}

showAlert(type: .warning) // 주의하세요!

특정 값에 추가 데이터를 저장해야할 때

  • 예제: 날씨 정보를 저장하는 경우
  • rainy(amount: Int), cloudy(description: String)처럼 각각 다른 데이터를 저장해야 할 때 연관 값을 사용할 수 있다
enum Weather {
    case sunny
    case rainy(amount: Int)
    case cloudy(description: String)
}

let todayWeather = Weather.rainy(amount: 10)

switch todayWeather {
case .sunny:
    print("날씨가 맑아요!")
case .rainy(let amount):
    print("비가 \(amount)mm 내려요!")
case .cloudy(let description):
    print("구름이 \(description) 상태예요.")
}

특정 값에 기본 값을 부여할 때

  • 예제: 요일을 숫자로 저장할 때
  • rawValue(원시값)를 사용하면 특정 값을 숫자나 문자열로 자동 변환할 수 있다
enum Weekday: Int {
    case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday
}

let today = Weekday.friday
print(today.rawValue) // 5

특정 타입의 동작을 정의해야 할 때

  • 예제: 특정 열거형에 메서드를 추가할 때
  • description()을 추가해서 각 상태에 따라 메시지를 다르게 반환할 수 있다
enum DeviceStatus {
    case on, off, standby

    func description() -> String {
        switch self {
        case .on:
            return "장치가 켜져 있습니다."
        case .off:
            return "장치가 꺼져 있습니다."
        case .standby:
            return "대기 모드입니다."
        }
    }
}

let status = DeviceStatus.standby
print(status.description()) // 대기 모드입니다.

원시값(Raw Value)

  • 열거형의 각 case에 숫자, 문자열, 문자 등의 기본 값을 미리 할당하는 것이다

원시값 기본 사용법

enum Weekday: Int {
    case monday = 1
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
    case sunday
}
  • monday = 1로 설정하면, 이후 case들은 자동으로 2, 3, 4... 증가
  • Weekday.tuesday.rawValue를 사용하면 숫자로 변환할 수 있다
let today = Weekday.friday
print(today.rawValue) // 5

원시값을 설정하지 않으면 0부터 시작

  • 원시값을 설정하지 않으면 자동으로 0부터 시작해서 1, 2, 3... 순서로 증가한다
enum Number: Int {
    case zero
    case one
    case two
    case three
}

print(Number.zero.rawValue)  // 0
print(Number.one.rawValue)   // 1
print(Number.two.rawValue)   // 2
  • Number.zero의 rawValue는 자동으로 0이 할당된다
  • 이후 값들은 자동으로 1씩 증가하면서 할당된다

String 타입을 원시값으로 사용하기

  • Int 뿐만 아니라 String도 사용할 수 있다
enum Compass: String {
    case north = "북쪽"
    case south = "남쪽"
    case east = "동쪽"
    case west = "서쪽"
}

let direction = Compass.north
print(direction.rawValue) // "북쪽"
  • rawValue를 사용하면, case가 가지고 있는 문자열 값을 가져올 수 있다
  • north.rawValue ➡️ "북쪽"

String 타입으로 설정했지만 원시값을 지정하지 않으면?

  • enumString 타입으로 지정하면, case 이름 그대로 자동으로 원시값이 설정된다
enum Fruit: String {
    case apple
    case banana
    case cherry
}

print(Fruit.apple.rawValue)   // "apple"
print(Fruit.banana.rawValue)  // "banana"
print(Fruit.cherry.rawValue)  // "cherry"
  • case apple"apple", case banana"banana"처럼 case 이름 그대로 문자열이 원시값이 된다
  • rawValue를 따로 설정하지 않아도, case 이름을 자동으로 원시값(String) 으로 사용할 수 있다

rawValue를 변수에 담아서 사용 가능

  • rawValue를 변수에 담아서 활용할 수도 있다
let todayRawValue = Weekday.friday.rawValue
print(todayRawValue) // 5
  • Weekday.friday.rawValue 값을 todayRawValue 변수에 저장할 수 있다
  • 이후에 다른 연산이나 데이터 변환에 활용할 수도 있다

원시값을 사용하여 열거형을 생성하기

  • rawValue를 사용해서 열거형 인스턴스를 만들 수도 있다
if let day = Weekday(rawValue: 3) {
    print("오늘은 \(day)입니다.") // "오늘은 wednesday입니다."
} else {
    print("해당하는 요일이 없습니다.")
}
  • rawValue를 사용하면 숫자(또는 문자열) → 열거형 변환이 가능하다
  • 하지만, 없는 값(예: Weekday(rawValue: 10))을 넣으면 nil이 반환된다

원시값은 Hashable한 타입이면 전부 사용 가능 (하지만 대부분은 IntString을 사용)

  • 원시값으로 사용할 수 있는 타입은 Hashable 프로토콜을 준수해야 한다
  • 즉, Int, Double, String, Character 등을 사용할 수 있다
enum StatusCode: Int {
    case success = 200
    case notFound = 404
    case serverError = 500
}

enum Alphabet: Character {
    case a = "A"
    case b = "B"
    case c = "C"
}

enum Currency: Double {
    case usd = 1.0
    case eur = 0.85
    case jpy = 110.0
}

print(StatusCode.success.rawValue) // 200
print(Alphabet.a.rawValue) // "A"
print(Currency.eur.rawValue) // 0.85
  • 단, Tuple, Array, Dictionary 같은 Hashable하지 않은 타입은 사용 불가

원시값을 이용해 열거형 생성 시, Optional 타입이 된다

  • 열거형을 원시값을 사용해서 생성할 때, 값이 없을 수도 있기 때문에 Optional 타입이 된다
let someDay = Weekday(rawValue: 3)
print(someDay) // Optional(Weekday.wednesday)

let invalidDay = Weekday(rawValue: 10)
print(invalidDay) // nil
  • Weekday(rawValue: 3)Optional(Weekday.wednesday)
  • Weekday(rawValue: 10) → 존재하지 않는 값이므로 nil 반환

값이 있는지 확인하려면 if let 또는 guard let을 사용해야 한다

if let validDay = Weekday(rawValue: 3) {
    print("오늘은 \(validDay)입니다.") // "오늘은 wednesday입니다."
} else {
    print("잘못된 요일입니다.")
}

연관값(Associated Value)

  • 열거형의 각 case에 다른 타입의 데이터를 저장할 수 있는 개념
  • 열거형의 연관값은 구체적인 추가정보를 저장하기 위해 사용
  • rawValue는 모든 case가 동일한 타입이지만, 연관값은 각 case마다 다른 타입을 가질 수 있다
  • 연관값은 Tuple 형태로 여러 개의 값을 저장할 수도 있다

기본적인 연관값 사용 예제

enum Weather {
    case sunny
    case rainy(amount: Int)
    case cloudy(description: String)
}

let todayWeather = Weather.rainy(amount: 10)

switch todayWeather {
case .sunny:
    print("맑은 날씨!")
case .rainy(let amount):
    print("비가 \(amount)mm 내려요!")
case .cloudy(let description):
    print("구름이 \(description) 상태예요.")
}
  • case마다 다른 데이터를 저장할 수 있다
  • rainy(amount: Int) ➡️ amount 값을 저장
  • cloudy(description: String) ➡️ description 값을 저장

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

구분원시값(Raw Value)연관값(Associated Value)
자료형 선언 방식열거형 전체가 하나의 기본 타입(Int, String, DoubleHashable한 타입)을 가진다case가 개별적으로 다른 타입을 가질 수 있다
선언 형식enum EnumName: Type { case a = value }enum EnumName { case a(value: Type) }
값의 저장 시점열거형 선언 시점에 고정된다(컴파일 타임에 정해진다)열거형을 생성할 때 결정된다(런타임에 저장된다)
배타적 특성rawValue는 모든 case가 같은 타입을 가진다연관값을 가지면 rawValue를 사용할 수 없다(서로 배타적)

연관값의 활용

enum UserStatus {
    case online(name: String, age: Int)
    case offline(reason: String)
}

let user1 = UserStatus.online(name: "Royce", age: 25)
let user2 = UserStatus.offline(reason: "네트워크 오류")

switch user1 {
case .online(let name, let age):
    print("\(name)(\(age))님이 온라인 상태입니다.") 
case .offline(let reason):
    print("사용자가 오프라인 상태입니다. 이유: \(reason)")
}
  • case .online(name: String, age: Int) ➡️ 두 개의 값을 저장할 수 있다
  • Tuple을 사용해서 여러 개의 값을 한 번에 받을 수 있다
switch user1 {
case let .online(name, age):
    print("\(name)(\(age))님이 온라인 상태입니다.") 
case let .offline(reason):
    print("사용자가 오프라인 상태입니다. 이유: \(reason)")
}
  • 이와 같이 case let을 사용하면 .online(let name, let age)와 같으며, case 전체에 대해 한 번에 변수 선언을 할 수 있다

Swift의 Optional은 내부적으로 열거형(Enum)으로 구현되어 있다

옵셔널(Optional)

  • 옵셔널은 값이 없을 수도 있는 변수나 상수를 표현하는 타입이다
  • 즉, 변수가 nil일 가능성이 있으면 옵셔널로 선언해야 한다
var name: String? = "Royce"
print(name) // Optional("Royce")

name = nil
print(name) // nil
  • String? 타입의 변수 name"Royce" 값을 가질 수도 있고, nil을 가질 수도 있다
  • 옵셔널이 아니라면 nil을 저장할 수 없다 (Stringnil이 될 수 없는 타입)

옵셔널은 내부적으로 열거형(Enum)이다

  • Swift에서는 옵셔널(Optional)이 내부적으로 열거형(Enum)으로 정의되어 있다
enum Optional<Wrapped> {
    case some(Wrapped)
    case none
}
  • 옵셔널은 제네릭을 사용한 열거형이다
  • Wrapped는 옵셔널이 감싸고 있는 실제 값의 타입 (예: String, Int, Double 등)
  • case none ➡️ 값이 없는 상태 (nil을 의미)
  • case some(Wrapped) ➡️ 실제 값을 감싸고 있는 상태

.nonenil이 동일한 이유

  • 옵셔널의 .nonenil은 같은 의미이다
  • 즉, nil은 사실 옵셔널 열거형에서 .none을 의미하는 단축 표현이다
var name1: String? = nil
var name2: String? = .none

print(name1 == name2) // true
  • nil.none의 축약형 표현이므로 동일한 값으로 취급된다
  • 즉, nil을 사용하면 내부적으로 .none으로 변환된다

옵셔널 값이 있을 때 (.some(value))

var age: Int? = 25

print(age)  // Optional(25)
print(age == .some(25))  // true
  • age = 25는 사실 내부적으로 .some(25)로 저장된다

옵셔널을 열거형처럼 직접 사용해 보기

  • 옵셔널은 열거형이므로, switch 문을 사용해서 직접 패턴 매칭을 할 수 있다
var age: Int? = 25

switch age {
case .none:
    print("나이가 없습니다.")
case .some(let value):
    print("나이는 \(value)살입니다.")
}


// 실행 결과
나이는 25살입니다.
  • age.none이면 “나이가 없습니다.” 출력
  • age.some(25)이면 “나이는 25살입니다.” 출력

옵셔널 바인딩과 열거형의 관계

  • 옵셔널을 사용할 때 가장 많이 사용하는 옵셔널 바인딩(if let, guard let)도 열거형 패턴 매칭을 간략화한 문법이다

if let 옵셔널 바인딩

var nickname: String? = "Swift Master"

if let unwrappedName = nickname {
    print("닉네임: \(unwrappedName)")
} else {
    print("닉네임이 없습니다.")
}


// 실행 결과
닉네임: Swift Master

위 코드를 switch 문을 사용해서 풀어보기

switch nickname {
case .some(let unwrappedName):
    print("닉네임: \(unwrappedName)")
case .none:
    print("닉네임이 없습니다.")
}
  • if let 문법은 사실 내부적으로 switch 문을 사용해 .some.none을 판별하는 것이다
  • 즉, 옵셔널 바인딩은 열거형 패턴 매칭을 간략화한 문법이다

옵셔널 체이닝과 열거형

  • 옵셔널 체이닝(?.)도 내부적으로는 옵셔널이 열거형이기 때문에 가능한 기능이다
struct Person {
    var name: String
    var pet: String?
}

let person = Person(name: "Royce", pet: nil)

print(person.pet?.uppercased() ?? "반려동물이 없습니다.") 


// 실행 결과
반려동물이 없습니다.
  • pet이 nil(.none)이므로 uppercased() 호출이 무시된다
  • ??를 사용해서 기본값 "반려동물이 없습니다." 반환

옵셔널을 활용한 기능

  • 옵셔널(Optional)은 열거형이기 때문에 열거형이 가진 패턴 매칭, switch 문 활용, 바인딩 기능을 활용할 수 있다

guard let을 활용한 옵셔널 처리

func printAge(_ age: Int?) {
    guard let validAge = age else {
        print("나이 정보가 없습니다.")
        return
    }
    print("나이는 \(validAge)살입니다.")
}

printAge(nil)  // 나이 정보가 없습니다.
printAge(30)   // 나이는 30살입니다.
  • guard let을 사용하면 값이 없을 경우 바로 return 가능
  • if let보다 더 깔끔한 흐름 제어 가능

열거형(enum)과 switch 문 활용

기본적인 enumswitch 문 활용

// 열거형 정의
enum Day {
    case monday, tuesday, wednesday, thursday, friday, saturday, sunday
}

func printMessage(for day: Day) {
    switch day {
    case .monday:
        print("월요일입니다. 새로운 한 주가 시작됩니다!")
    case .tuesday:
        print("화요일입니다. 아직 멀었어요.")
    case .wednesday:
        print("수요일입니다. 주말이 반쯤 왔어요.")
    case .thursday:
        print("목요일입니다. 조금만 더 힘내세요!")
    case .friday:
        print("금요일입니다! 곧 주말이에요.")
    case .saturday, .sunday:
        print("주말입니다! 쉬세요~")
    }
}

// 함수 호출
printMessage(for: .wednesday)
  • enum Day를 선언하여 요일을 정의한다
  • switch 문을 활용하여 각 요일에 따라 다른 메시지를 출력하도록 한다

옵셔널 열거형 처리

Unwrapping 후 사용하기

  • 옵셔널을 .some(value) 패턴으로 Unwrapping 후 switch 문에서 사용
let z: Status? = .success

switch z {
case .some(let value):  // 옵셔널을 먼저 Unwrapping
    switch value {      // 내부 열거형 값을 다시 분기 처리
    case .success:
        print("성공했습니다! (Unwrapped)")
    case .failure:
        print("실패했습니다! (Unwrapped)")
    }
case .none:
    print("상태 정보가 없습니다! (Unwrapped)")
}
  • 첫 번째 switch 문에서 .some(value)을 사용하여 옵셔널을 명확히 Unwrapping한다
  • 내부 switch 문에서 언래핑된 값을 다시 검사하여 처리를 수행
  • case .none을 활용하여 nil인 경우를 처리할 수 있음

Swift의 편의 기능을 활용한 옵셔널 열거형 처리

  • 원칙적으로는 switch 문에서 옵셔널 값을 Unwrapping 한 후 사용해야 하지만, Swift는 자동 Unwrapping 기능을 제공하여 이를 생략할 수 있다
// 열거형 정의 (연관 값 없음)
enum Status {
    case success
    case failure
}

// 옵셔널 열거형을 선언하여 `switch` 문에서 활용
let x: Status? = .success

switch x {  // Unwrapping 없이 사용 (Swift의 편의 기능)
case .success:
    print("성공했습니다! (자동 Unwrapping)")
case .failure:
    print("실패했습니다! (자동 Unwrapping)")
case nil:
    print("상태 정보가 없습니다!") // 옵셔널이 nil인 경우
}

// 옵셔널을 nil로 변경하여 다시 실행
let y: Status? = nil

switch y {
case .success:
    print("성공했습니다! (자동 Unwrapping)")
case .failure:
    print("실패했습니다! (자동 Unwrapping)")
case nil:
    print("상태 정보가 없습니다!") // 옵셔널이 nil인 경우
}
  • switch 문에서 옵셔널을 별도로 Unwrapping 하지 않고 내부 값에 직접 접근할 수 있다
  • Swift가 자동으로 옵셔널을 해제하여 switch 문 내에서 직접 case .success, case .failure를 사용할 수 있다
  • case nil을 추가하여 옵셔널이 nil일 경우를 처리

연관값(Associated Value)가 있는 경우

연관 값(Associated Values)이 있는 enumswitch 문 활용

// 연관 값이 있는 열거형 정의
enum OrderStatus {
    case pending(orderID: Int)
    case processing(orderID: Int, estimatedTime: Int)
    case shipped(orderID: Int, trackingNumber: String)
    case delivered(orderID: Int)
    case cancelled(orderID: Int, reason: String)
}

func checkOrderStatus(_ status: OrderStatus) {
    switch status {
    case .pending(let orderID):
        print("주문 #\(orderID) 대기 중입니다.")
    case .processing(let orderID, let estimatedTime):
        print("주문 #\(orderID)는 처리 중입니다. 예상 소요 시간: \(estimatedTime)분")
    case .shipped(let orderID, let trackingNumber):
        print("주문 #\(orderID)는 배송 중입니다. 트래킹 번호: \(trackingNumber)")
    case .delivered(let orderID):
        print("주문 #\(orderID)가 배송 완료되었습니다.")
    case .cancelled(let orderID, let reason):
        print("주문 #\(orderID)가 취소되었습니다. 이유: \(reason)")
    }
}

// 함수 호출
checkOrderStatus(.pending(orderID: 101))
checkOrderStatus(.processing(orderID: 102, estimatedTime: 30))
checkOrderStatus(.shipped(orderID: 103, trackingNumber: "AB123456789"))
checkOrderStatus(.delivered(orderID: 104))
checkOrderStatus(.cancelled(orderID: 105, reason: "재고 부족"))
  • OrderStatus 열거형은 각 상태에 맞게 추가적인 데이터를 저장할 수 있다
  • switch 문을 사용해 연관 값을 추출하여 활용

조건문에서 enum 활용

if case를 사용한 연관 값 추출

let order: OrderStatus = .processing(orderID: 200, estimatedTime: 45)

if case .processing(let orderID, let estimatedTime) = order {
    print("주문 #\(orderID)는 현재 처리 중이며, 예상 소요 시간은 \(estimatedTime)분입니다.")
} else {
    print("이 주문은 처리 중이 아닙니다.")
}
  • if case 문을 사용해 특정 enum case에 해당하는지 검사하면서 연관 값을 추출할 수 있다
  • else 문을 사용해 다른 경우도 처리 가능하다

guard case를 사용한 조기 반환

func processOrder(_ status: OrderStatus) {
    guard case .processing(let orderID, let estimatedTime) = status else {
        print("이 주문은 현재 처리 중이 아닙니다.")
        return
    }
    
    print("주문 #\(orderID)를 처리 중입니다. 예상 소요 시간: \(estimatedTime)분")
}

// 함수 호출
processOrder(.processing(orderID: 201, estimatedTime: 20))
processOrder(.delivered(orderID: 202))
  • guard case를 사용하면 특정 enum 값이 아닐 경우 조기에 반환할 수 있어 가독성이 좋아진다

반복문에서 enum 활용 (for case let)

for case let을 활용한 반복문에서 연관 값 추출

let orders: [OrderStatus] = [
    .pending(orderID: 301),
    .processing(orderID: 302, estimatedTime: 25),
    .shipped(orderID: 303, trackingNumber: "XY789456123"),
    .delivered(orderID: 304),
    .cancelled(orderID: 305, reason: "결제 오류")
]

// `for case let`을 사용하여 특정 `enum` 값만 추출하여 출력
for case let .processing(orderID, estimatedTime) in orders {
    print("처리 중인 주문 #\(orderID), 예상 소요 시간: \(estimatedTime)분")
}
  • for case let을 사용하면 특정 enum 값만 필터링하여 연관 값을 추출할 수 있다
  • .processing 상태의 주문만 출력된다

while case 문을 활용한 상태 변경

var currentStatus: OrderStatus = .pending(orderID: 400)

while case let .pending(orderID) = currentStatus {
    print("주문 #\(orderID)는 아직 처리되지 않았습니다.")
    currentStatus = .processing(orderID: orderID, estimatedTime: 15) // 상태 변경
}

print("주문 상태가 업데이트되었습니다!")
  • while case let을 사용하여 특정 enum 상태에서만 반복 실행 가능
  • 내부에서 상태를 업데이트하여 while 문을 빠져나올 수 있다

옵셔널 패턴

  • Swift에서 옵셔널은 사실 enum 타입으로 구현되어 있다
enum Optional<Wrapped> {
    case some(Wrapped)
    case none
}
  • 즉, 옵셔널 값은 내부적으로 .some(값) 또는 .none(nil)로 표현된다
  • 기존의 열거형 케이스 패턴을 활용하면 옵셔널을 다음과 같이 처리할 수 있다

기본적인 옵셔널 패턴 활용

let value: Int? = 10

// 1) 열거형 케이스 패턴
switch value {
case .some(let num):
    print("값: \(num)")
case .none:
    print("nil")
}

// 2) 옵셔널 패턴 (더 간결한 문법)
switch value {
case let num?:  // .some을 ?로 대체한 표현
    print("값: \(num)")
case nil:
    print("nil")
}
  • 옵셔널 패턴(let 변수?)을 사용하면 .some(let num) 대신 let num?로 간략하게 표현할 수 있다

옵셔널 패턴 vs 열거형 케이스 패턴

  • 옵셔널 값을 다룰 때, 다양한 경우에서 옵셔널 패턴이 더 간결한 코드를 작성할 수 있게 도와준다

if case문에서 활용

let number: Int? = 5

// 1) 열거형 케이스 패턴
if case .some(let x) = number {
    print("값: \(x)")
}

// 2) 옵셔널 패턴
if case let x? = number {  // "옵셔널을 벗겨내고, x에 값을 할당"
    print("값: \(x)")
}
  • 옵셔널 패턴을 활용하면 .some을 제거하고 let x?로 간결하게 표현할 수 있다

for 반복문에서 옵셔널 패턴 활용

  • 옵셔널 값이 포함된 배열을 순회할 때도 옵셔널 패턴을 활용하면 nil을 자동으로 필터링할 수 있다

옵셔널 배열 처리

let numbers: [Int?] = [nil, 2, 3, nil, 5]

// 1) 열거형 케이스 패턴 사용
for case .some(let num) in numbers {
    print("값: \(num)")
}

// 2) 옵셔널 패턴 사용 (더 간결한 표현)
for case let num? in numbers {
    print("값: \(num)")
}
  • 옵셔널 패턴을 사용하면 nil 값이 자동으로 무시되므로 if let을 사용하여 옵셔널을 해제할 필요가 없다

옵셔널 패턴 활용: guard case

  • guard문에서도 옵셔널 패턴을 활용할 수 있다

guard문에서 옵셔널 패턴 사용

func checkValue(_ input: Int?) {
    guard case let value? = input else {
        print("값이 없습니다.")
        return
    }
    print("값: \(value)")
}

checkValue(8)   // 값: 8
checkValue(nil) // 값이 없습니다.
  • 옵셔널 패턴을 사용하면 if let과 비슷하게 옵셔널 바인딩을 수행할 수 있으며, guard 문과 함께 사용하면 더욱 직관적인 코드 작성이 가능하다

열거형(Enum)의 확장성과 문제점

Swift의 enum은 기존의 정의된 값 이외에도 새로운 값이 추가될 가능성이 있다. 특히 API, SDK, 또는 협업 프로젝트에서 열거형이 업데이트될 경우 기존 코드가 제대로 동작하는지를 보장하기 어려워진다.

예를 들어, 로그인에 관한 열거형을 정의한 후, 새로운 로그인 방식이 추가될 경우를 생각해보자

enum LoginProvider: String {  // 초기에는 3가지 케이스
    case email
    case facebook
    case google
    case kakaotalk  // 새로운 로그인 방식 추가됨!
}

let userLogin = LoginProvider.email

여기서 switch 문을 사용해 로그인 방식을 처리한다고 가정하면, 문제가 발생할 수 있다


switch 문에서 발생할 수 있는 문제

  • switch 문은 열거형의 모든 경우를 다루도록 설계되어 있지만, 새로운 케이스가 추가되면 기존의 switch 문이 그대로 동작할지 확신할 수 없다
switch userLogin {
case .email:
    print("이메일 로그인")
case .facebook:
    print("페이스북 로그인")
// case .google:  // ✅ 주석 처리되어 있음! 버그 가능성 🚨
//     print("구글 로그인")
default:
    print("구글 로그인")  // 🛑 과연 이 방식이 안전할까?
}

문제점

  • 새로운 case(.kakaotalk)가 추가되었지만, switch 문은 이를 고려하지 않았다
  • 기본 default 블록이 존재하지만, 새로운 케이스가 잘못된 방식으로 처리될 수도 있다
  • 프로젝트 규모가 커질수록 개발자가 이 문제를 찾기 어려워진다

@unknown default 키워드

  • Swift 5.0부터 @unknown 키워드를 default 블록과 함께 사용하면, 컴파일러가 새로운 열거형 케이스가 추가되었을 때 경고를 표시해준다
switch userLogin {
case .email:
    print("이메일 로그인")
case .facebook:
    print("페이스북 로그인")
case .google:
    print("구글 로그인")
@unknown default:
    print("그 외의 모든 경우")
}

@unknown default의 장점

컴파일 시 경고 표시

  • 새로운 case(.kakaotalk)가 추가되었을 때, 컴파일러가 경고를 띄워 미처 처리되지 않은 케이스가 있음을 알려준다

예상치 못한 케이스를 안전하게 처리

  • 기존의 default 블록과 다르게, 새로운 case를 놓쳤을 경우 개발자가 이를 인지할 수 있도록 돕는다

Swift API 변경 대응에 유리

  • Apple의 API 또는 타인의 라이브러리에서 열거형을 변경했을 경우, 코드가 미리 알려주기 때문에 유지보수가 쉬워진다

@unknown 키워드 사용 시 주의할 점

  • @unknown default는 반드시 default 블록과 함께 사용해야 한다
  • 필수적으로 switch 문에서 모든 기존 케이스를 명확하게 작성하는 것이 좋다
  • API 변경이 잦은 프로젝트에서는 @unknown default가 안전한 선택이 될 수 있다
profile
iOS 개발자 지망생

0개의 댓글