반응형
Swift에서 옵셔널 기본값: 중첩 if let vs a ?? b
옵셔널을 안전하게 다루는 방법은 여러 가지가 있습니다. 그중 중첩 if let과 nil‑병합 연산자 ?? 는 가장 자주 쓰입니다. 이 문서는 두 방식의 의도 차이·가독성·성능 특성과 함께, 실무에서 바로 쓸 수 있는 예제를 정리했습니다.
1. 한눈에 요약
| 상황 | 권장 패턴 | 이유 |
| “값이 있으면 쓰고, 없으면 기본값을 쓰고 싶다” | a ?? default | 간결·표현적, 기본값 표현은 지연 평가(필요할 때만 계산) |
| 다단계 옵셔널 체이닝 + 기본값 | user?.team?.name ?? "Unknown" | 스코프 오염 없이 한 줄 해결 |
| 값이 있으면 추가 검증/분기가 필요 | if let / guard let | 분기/로깅/early‑return 등 제어 흐름에 적합 |
| 여러 값 중 첫 번째 non‑nil선택 | a ?? b ?? c | 오른쪽 결합, 자연스러운 폴백 체인 |
| 실패를 nil로 흡수한 뒤 기본값 | try? decode(...) ?? fallback | try? + ??조합이 명확 |
| 성능 민감 + 기본값 계산이 무거움 | a ?? expensive() | expensive()는 a가 nil일 때만실행됨 |
핵심: 단순 “기본값 폴백”은 ??가 표준. 분기/로깅/조기 종료가 필요하면 if let/guard let을 사용하세요.
2. 기본 형태 비교
2.1 a ?? b (nil‑병합 / 기본값 폴백)
let displayName = user.nickname ?? user.fullName ?? "Guest"
- 왼쪽이 non‑nil이면 그 값을 그대로 반환, nil이면 오른쪽을 평가합니다.
- b는 지연(lazy) 평가됩니다. (왼쪽이 non‑nil이면 b는 계산되지 않음)
2.2 중첩 if let
let name: String
if let nick = user.nickname {
name = nick
} else if let full = user.fullName {
name = full
} else {
name = "Guest"
}
- 로깅, 메트릭 집계, 얼럿 등 부가 동작이 필요할 때 적합합니다.
3. 체이닝과 결합 예제
3.1 옵셔널 체이닝 + 기본값
let city = profile?.address?.city ?? "N/A"
3.2 try? + 기본값
let model = (try? JSONDecoder().decode(Model.self, from: data)) ?? .empty
3.3 첫 번째 non‑nil 선택 (오른쪽 결합)
let token = runtimeOverride ?? cachedToken ?? keychain.token ?? ""
3.4 사전 조회 + 기본값
let n = counts["apple"] ?? 0 // Dictionary 서브스크립트는 Optional 반환
3.5 빈 문자열/컬렉션을 nil 취급하고 기본값 주기
extension Optional where Wrapped == String {
var nilIfEmpty: String? { self?.isEmpty == true ? nil : self }
}
let title = inputTitle.nilIfEmpty ?? "제목 없음"
4. ?? 의 지연 평가(lazy evaluation)
오른쪽 피연산자는 필요할 때만 평가됩니다.
func fetchFromNetwork() -> String { print(network); return remote }
let value1: String? = local
let result1 = value1 ?? fetchFromNetwork() // network 출력 없음
let value2: String? = nil
let result2 = value2 ?? fetchFromNetwork() // network 출력됨
- 기본값 계산이 **무거운 연산**일 때??`는 특히 유리합니다.
5. if let / guard let 을 선택해야 하는 경우
5.1 값 유무에 따라 행동이 달라지는 분기
if let img = cache[id] {
log.info("cache hit")
imageView.image = img
} else {
log.info("miss, fetch")
imageView.image = try await loader.fetch(id)
}
5.2 조기 종료(Early Exit)와 검증
func submit(_ form: Form?) {
guard let form, form.isValid else {
toast("입력을 확인하세요")
return
}
send(form)
}
6. 가독성·유지보수 관점 가이드
- 단순 값 선택은 ?? 로 표현을 값 중심으로 작성
- 제어 흐름(로깅/분기/리턴/throw)이 개입되면 if let/guard let
- 삼항 연산자와 섞일 때는 괄호로 의도를 명확히
- 길어지면 중간 변수를 도입하여 단계 분리
7. 실무 예제 모음
7.1 포맷팅과 기본값
let priceText = formatter.string(from: price as NSNumber) ?? "—"
7.2 SwiftUI 바인딩 값 폴백
Text(user.nickname?.uppercased() ?? "GUEST")
7.3 환경 변수/설정 오버라이드
let endpoint = ProcessInfo.processInfo.environment["API_ENDPOINT"]
?? UserDefaults.standard.string(forKey: "api_endpoint")
?? "https://api.example.com"
7.4 URL 생성 실패 시 폴백
let url = URL(string: raw) ?? URL(string: "https://example.com")!
7.5 매핑 실패 시 기본 모델
let item = source.first(where: { $0.id == id }) ?? Item.placeholder
8. 성능 관점 메모
- ?? 와 간단한 if let 은 실제로 매우 비슷합니다. 차이가 있다면 일반적으로 ?? 쪽이 더 간결하고 분기 오버헤드가 적은 표현을 유도합니다.
- 중첩 if let 은 다단계 분기/로깅에서는 필수지만, 단순 폴백이라면 가독성·유지보수 측면에서 불리할 수 있습니다.
- 항상 Release(-O) 빌드에서 측정하세요. 디버그 빌드는 최적화가 꺼져 있습니다.
9. 흔한 실수와 교정
1) 기본값 계산을 미리 해두는 실수
let d = expensiveDefault() // 🚫 항상 계산됨
let name = user.name ?? d // 불필요한 비용
교정
let name = user.name ?? expensiveDefault() // ✅ 필요할 때만
2) 빈 문자열/배열을 nil과 혼동
let shown = (title ?? "Untitled") // title == "" 인 경우도 "Untitled"를 원했나요?
교정
let shown = (title?.isEmpty == false ? title! : "Untitled")
// 또는 위의 nilIfEmpty 헬퍼 활용
3) 불필요한 중첩 if let
if let a = a { result = a } else { result = b } // 🚫 장황
let result = a ?? b // ✅ 간결
10. 사용 예제
import Foundation
func measure(_ name: String, _ block: () -> Void) {
let t1 = CFAbsoluteTimeGetCurrent(); block(); let t2 = CFAbsoluteTimeGetCurrent()
print(name, ":", t2 - t1, "sec")
}
let N = 200_000
let arr: [String?] = (0..<N).map { $0 % 3 == 0 ? nil : "v\($0)" }
measure("??") {
_ = arr.map { $0 ?? "d" }
}
measure("if let") {
_ = arr.map { s -> String in
if let s { return s } else { return "d" }
}
}
11. 정리
- 기본값 폴백은 ??가 기본값.
- 분기/로깅/조기 종료가 필요하면 if let/guard let.
- ??의 오른쪽은 지연 평가되므로, 비용이 큰 기본값 계산에 유리.
- 빈 값 처리(예: "", [])는 nil과 구분하고, 필요하면 유틸(예: nilIfEmpty)로 명시하세요.
반응형
'Dev Study > Swift' 카테고리의 다른 글
| TCA Study - State · Action · Reducer · Store — TCA의 핵심 구조 완전 정복 (0) | 2025.12.16 |
|---|---|
| TCA Study - Swift 개발자가 TCA를 알아야 하는 이유 (0) | 2025.12.16 |
| Swift에서 정렬: sort vs sorted (1) | 2025.11.11 |
| Swift에서 배열 초기화: 반복 append vs Array(repeating:count:) (0) | 2025.11.11 |
| Swift에서 문자열 비교 (0) | 2025.11.11 |
| Swift에서 ARC 최적화: weak vs unowned (0) | 2025.11.11 |
| Swift에서 @inlinable / @inline(__always) (0) | 2025.11.11 |
| Swift에서 구조체(Value Type) 기반 설계 (0) | 2025.11.11 |


