왜 이 내용을 정리하나
설정 값이나 API 응답처럼 딕셔너리가 여러 단계로 중첩된 데이터를 다룰 때, 중간에 어떤 키가 없을 수 있다. 이때 강제 언래핑(!)을 쓰면 런타임에 크래시가 발생한다. 옵셔널 체이닝은 중간 단계가 nil이면 나머지 접근을 건너뛰고 전체 결과를 nil로 만들어 주기 때문에, 이런 상황을 안전하게 처리하는 기본 도구가 된다. 이 노트에서는 옵셔널 체이닝으로 중첩 딕셔너리를 조회하는 방법과 그 과정에서 생기는 함정을 정리해 본다.
개념 설명
Swift에서 딕셔너리의 subscript 접근 dict[key]는 항상 옵셔널을 반환한다. 키가 존재하면 값이 감싸진 옵셔널이, 없으면 nil이 나온다. 여기에 ?를 붙이면 옵셔널 체이닝이 된다. config[section]?[network]처럼 쓰면 config[section]이 nil일 때 뒤쪽의 subscript는 실행되지 않고 전체 식이 nil이 된다.
옵셔널 체이닝의 중요한 특징은 결과 타입이 한 번 더 옵셔널로 감싸지지 않는다는 점이다. 값 타입이 이미 옵셔널인 경우에는 평평하게 합쳐지므로, 여러 단계를 내려가도 최종 결과는 값 타입의 옵셔널 하나로 정리된다. 이 점을 이해하고 있어야 if let이나 ??로 결과를 받을 때 타입을 헷갈리지 않는다.
딕셔너리의 값 타입이 Any인 경우에는 한 가지 단계가 더 필요하다. config[section]?[network]의 결과는 Any? 타입이므로, 그 안의 딕셔너리로 쓰려면 as? [String: Any]로 다운캐스팅해야 한다. 캐스팅이 실패하면 역시 nil이 되므로, 캐스팅 실패도 옵셔널 체인의 일부로 자연스럽게 처리된다.
코드 예제
아래 코드는 서버 설정을 담은 중첩 딕셔너리에서 포트 번호를 조회하는 완전한 예제다. 저장소의 src/optional_lookup.swift와 같은 구조로 작성했다.
swift
let config: [String: [String: Any]] = [
'server': [
'network': ['port': 8080],
'timeout': 30
],
'client': [
'retry': ['limit': 3]
]
]
func lookupPort(in config: [String: [String: Any]], section: String) -> Int {
// 각 단계가 nil이면 체이닝 전체가 nil이 된다
if let port = (config[section]?['network'] as? [String: Any])?['port'] as? Int {
return port
}
return 80
}
print(lookupPort(in: config, section: 'server'))
print(lookupPort(in: config, section: 'client'))
print(lookupPort(in: config, section: 'missing'))
이 코드를 실행하면 8080, 80, 80이 순서대로 출력된다. 'server' 섹션에는 network와 port가 모두 있으므로 8080이 반환된다. 'client' 섹션에는 network 키가 없으므로 체이닝이 중간에 nil이 되어 기본값 80이 반환된다. 'missing' 섹션은 최상위 키 자체가 없으므로 역시 80이다. 세 경우 모두 크래시 없이 처리된다.
함수 안의 if let은 최종 결과가 nil인지 확인하는 역할을 한다. 체이닝 전체가 nil이면 분기를 건너뛰고 return 80이 실행된다. 기본값을 ??로 바꿔 쓸 수도 있지만, 이 예제처럼 if let으로 분기를 나누면 값이 없을 때의 처리를 함수 안에서 명시적으로 드러낼 수 있다.
헷갈리는 점
첫째, 옵셔널 체이닝은 nil을 만나면 전체를 nil로 만들지만, 실패 원인은 알려 주지 않는다. 'section이 없는지', 'network가 없는지', 'port의 타입이 틀린지'를 구분하려면 단계마다 나눠서 확인해야 한다. 원인 구분이 필요하면 체이닝 대신 단계별 guard let을 쓰는 편이 낫다.
둘째, as? Int 캐스팅이 실패하는 경우도 nil이 된다. 설정 파일에 포트를 문자열 'a8080'으로 써 두면 값은 존재하지만 캐스팅에 실패해 기본값 80이 반환된다. 값이 없는 것과 타입이 틀린 것이 같은 결과로 합쳐진다는 점을 기억해야 한다.
셋째, 딕셔너리 값 타입을 Any로 두면 컴파일러가 타입을 보장해 주지 않는다. 같은 구조라도 값 타입을 구체적인 구조체나 열거형으로 바꾸면 캐스팅 없이 안전하게 접근할 수 있다. 데이터 구조가 고정되어 있다면 Any 대신 구체 타입을 쓰는 것을 검토해 본다.
정리
옵셔널 체이닝은 중첩 딕셔너리에서 중간 키가 없어도 크래시 없이 nil을 전파하는 도구다. subscript 접근은 항상 옵셔널을 반환하므로 ?를 이어 붙이면 각 단계가 안전해진다. Any 값을 다룰 때는 as? 캐스팅도 체인의 일부로 nil을 만들 수 있다는 점에 주의해야 한다. 마지막 결과는 if let이나 ??로 받아 기본값을 명시하면 된다. 이 노트에서는 위 예제로 nil 전파와 기본값 처리의 흐름을 확인해 본다.
#Swift #옵셔널체이닝 #딕셔너리 #옵셔널 #공부노트
'Swift' 카테고리의 다른 글
| Swift let 상수에 재할당 시도 시 컴파일 에러 메시지 직접 확인하기 (0) | 2026.10.24 |
|---|---|
| Swift 구조체는 대입 시 복사된다: 값 타입 동작을 print로 검증하기 (0) | 2026.10.23 |
| 아카이브/배포 검증 에러 해결기: Invalid Bundle Identifier, Bad CFBundleExecutable, Shared bundle path (3) | 2025.08.17 |
| our app is missing support for the following URL schemes (0) | 2025.06.16 |
| Ios 배포 후 Log 보는법 (0) | 2025.06.16 |