| 타입 | 설명 | 예시 |
|---|---|---|
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" // ❌ 오타 발생!
| 타입 | 설명 | 예시 |
|---|---|---|
struct (구조체) | 여러 개의 값을 하나로 묶을 때 사용 | struct Person { var name: String; var age: Int } |
class (클래스) | 객체지향 프로그래밍에서 객체를 정의할 때 사용 | class Animal { var species: String } |
enum (열거형) | 관련된 값들의 묶음을 정의할 때 사용 | enum Weekday { case monday, tuesday } |
예를 들어, 요일을 표헌한다고 가정해보자. 열거형을 사용하면 아래처럼 명확하게 정의할 수 있다.
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) 상태예요.")
}
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()) // 대기 모드입니다.
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
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이 할당된다Int 뿐만 아니라 String도 사용할 수 있다enum Compass: String {
case north = "북쪽"
case south = "남쪽"
case east = "동쪽"
case west = "서쪽"
}
let direction = Compass.north
print(direction.rawValue) // "북쪽"
rawValue를 사용하면, case가 가지고 있는 문자열 값을 가져올 수 있다north.rawValue ➡️ "북쪽"enum을 String 타입으로 지정하면, 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를 변수에 담아서 활용할 수도 있다let todayRawValue = Weekday.friday.rawValue
print(todayRawValue) // 5
Weekday.friday.rawValue 값을 todayRawValue 변수에 저장할 수 있다if let day = Weekday(rawValue: 3) {
print("오늘은 \(day)입니다.") // "오늘은 wednesday입니다."
} else {
print("해당하는 요일이 없습니다.")
}
rawValue를 사용하면 숫자(또는 문자열) → 열거형 변환이 가능하다Weekday(rawValue: 10))을 넣으면 nil이 반환된다Int와 String을 사용)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
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 validDay = Weekday(rawValue: 3) {
print("오늘은 \(validDay)입니다.") // "오늘은 wednesday입니다."
} else {
print("잘못된 요일입니다.")
}
case에 다른 타입의 데이터를 저장할 수 있는 개념rawValue는 모든 case가 동일한 타입이지만, 연관값은 각 case마다 다른 타입을 가질 수 있다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) |
|---|---|---|
| 자료형 선언 방식 | 열거형 전체가 하나의 기본 타입(Int, String, Double 등 Hashable한 타입)을 가진다 | 각 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) ➡️ 두 개의 값을 저장할 수 있다switch user1 {
case let .online(name, age):
print("\(name)(\(age))님이 온라인 상태입니다.")
case let .offline(reason):
print("사용자가 오프라인 상태입니다. 이유: \(reason)")
}
case let을 사용하면 .online(let name, let age)와 같으며, case 전체에 대해 한 번에 변수 선언을 할 수 있다nil일 가능성이 있으면 옵셔널로 선언해야 한다var name: String? = "Royce"
print(name) // Optional("Royce")
name = nil
print(name) // nil
String? 타입의 변수 name은 "Royce" 값을 가질 수도 있고, nil을 가질 수도 있다nil을 저장할 수 없다 (String은 nil이 될 수 없는 타입)enum Optional<Wrapped> {
case some(Wrapped)
case none
}
Wrapped는 옵셔널이 감싸고 있는 실제 값의 타입 (예: String, Int, Double 등)case none ➡️ 값이 없는 상태 (nil을 의미)case some(Wrapped) ➡️ 실제 값을 감싸고 있는 상태.none과 nil이 동일한 이유.none과 nil은 같은 의미이다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() 호출이 무시된다??를 사용해서 기본값 "반려동물이 없습니다." 반환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 문 활용enum과 switch 문 활용// 열거형 정의
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 문을 활용하여 각 요일에 따라 다른 메시지를 출력하도록 한다.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인 경우를 처리할 수 있음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 하지 않고 내부 값에 직접 접근할 수 있다switch 문 내에서 직접 case .success, case .failure를 사용할 수 있다case nil을 추가하여 옵셔널이 nil일 경우를 처리enum과 switch 문 활용// 연관 값이 있는 열거형 정의
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 문을 사용해 연관 값을 추출하여 활용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 문을 사용해 다른 경우도 처리 가능하다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 값이 아닐 경우 조기에 반환할 수 있어 가독성이 좋아진다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 상태의 주문만 출력된다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 문을 빠져나올 수 있다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?로 간략하게 표현할 수 있다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 문func checkValue(_ input: Int?) {
guard case let value? = input else {
print("값이 없습니다.")
return
}
print("값: \(value)")
}
checkValue(8) // 값: 8
checkValue(nil) // 값이 없습니다.
if let과 비슷하게 옵셔널 바인딩을 수행할 수 있으며, guard 문과 함께 사용하면 더욱 직관적인 코드 작성이 가능하다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 키워드@unknown 키워드를 default 블록과 함께 사용하면, 컴파일러가 새로운 열거형 케이스가 추가되었을 때 경고를 표시해준다switch userLogin {
case .email:
print("이메일 로그인")
case .facebook:
print("페이스북 로그인")
case .google:
print("구글 로그인")
@unknown default:
print("그 외의 모든 경우")
}
@unknown default의 장점case(.kakaotalk)가 추가되었을 때, 컴파일러가 경고를 띄워 미처 처리되지 않은 케이스가 있음을 알려준다default 블록과 다르게, 새로운 case를 놓쳤을 경우 개발자가 이를 인지할 수 있도록 돕는다@unknown 키워드 사용 시 주의할 점@unknown default는 반드시 default 블록과 함께 사용해야 한다switch 문에서 모든 기존 케이스를 명확하게 작성하는 것이 좋다@unknown default가 안전한 선택이 될 수 있다