반응형
iOS 개발자가 많이 하는 실수 - Dictionary에서 key 존재 여부 확인 없이 강제 언래핑(!)
기존에 강제 언래핑에서 얘기했던 부분이긴한데 Dictionary에서 생각보다 자주 발생하는 부분입니다.
Swift 초보자가 가장 자주 발생시키는 크래시 중 하나가 바로:
let value = dict["key"]! // 여기가 터진다
형태의 코드입니다.
1. 문제 패턴
대표적인 코드 예시는 다음과 같습니다.
let json: [String: Any] = [
"name": "Jun",
"age": 20
]
let name = json["name"] as! String // 있다고 가정
let city = json["city"] as! String // ❌ city 키가 없으면 크래시
실제 런타임에서는 다음과 같은 크래시가 발생합니다.
Fatal error: Unexpectedly found nil while unwrapping an Optional value
혹은 타입 캐스팅이 틀리면:
Could not cast value of type '...'
2. Dictionary 서브스크립트의 반환 타입
핵심은 이 한 줄입니다.
let value = dict["key"]
여기서 value의 타입은 Optional입니다.
// [Key: Value] 타입의 dictionary라면
dict["key"] // => Value?
이것은 Swift 표준 라이브러리가 “해당 키가 없을 수 있다”는 것을
타입 레벨에서 명시적으로 표현하기 때문입니다.
그런데 여기에 강제 언래핑 !을 붙이면,
let value = dict["key"]! // 키가 없으면 크래시
Optional이 가지고 있는 안전장치를 스스로 해제하는 셈이 됩니다.
3. 왜 이 실수가 실무에서 자주 터질까?
- 샘플 데이터에는 항상 존재하는 키
- 테스트 JSON에는 항상 "city"가 들어있어서 문제가 안 보임
- 실제 운영 환경에서는 해당 필드가 빠진 케이스가 존재
- 서버 스펙 변경 / 버전 차이
- 서버 개발자가 필드 이름을 살짝 변경하거나 새 버전을 배포
- 클라이언트는 구버전 처리를 유지하고 있다가 크래시
- 타입이 다르게 들어오는 경우
- "age": "20" (String)으로 들어오는데
- as! Int로 강제 캐스팅하여 크래시
4. 안전한 처리 패턴
4-1. Optional Binding (if let / guard let)
if let city = json["city"] as? String {
print(city)
} else {
print("도시 정보 없음")
}
혹은:
guard let city = json["city"] as? String else {
print("도시 정보 없음")
return
}
print(city)
- 값이 있을 때만 안전하게 사용
- 값이 없을 때의 처리 분기를 명확하게 할 수 있음
4-2. 기본값 제공 (?? 연산자)
UI에 기본 메시지를 보여주고 싶을 때 유용합니다.
let city = (json["city"] as? String) ?? "도시 정보 없음"
4-3. 타입 안정성을 위해 모델 + Codable 사용
사실 가장 좋은 방법은
Dictionary를 직접 파고들기보다 Codable을 사용하는 것입니다.
struct User: Decodable {
let name: String
let age: Int?
let city: String?
}
JSON을 decode하고 나면:
let user = try decoder.decode(User.self, from: data)
print(user.city ?? "도시 정보 없음")
- 키 누락, 타입 불일치는 decode 과정에서 에러 또는 nil로 처리
- 강제 언래핑을 쓸 이유가 줄어듦
4-4. 딕셔너리 자체에 기본값을 줄 수 있는 API
Swift Dictionary는 기본값을 줄 수 있는 서브스크립트도 제공합니다.
let countByID = ["a": 1, "b": 2]
let countC = countByID["c", default: 0] // 0
- 키가 없으면 default 값 사용
- Optional이 아니라 Non-Optional로 받기 때문에 코드가 간결해짐
5. 실무 스타일 예시
나쁜 예
func handle(json: [String: Any]) {
let name = json["name"] as! String
let age = json["age"] as! Int // 타입/존재 보장 X
let city = json["city"] as! String // ❌ 크래시 위험
print(name, age, city)
}
개선 예 (Dictionary 기반 그대로 쓴다면)
func handle(json: [String: Any]) {
guard let name = json["name"] as? String else {
print("이름 없음")
return
}
let age = json["age"] as? Int
let city = json["city"] as? String ?? "도시 정보 없음"
print(name, age ?? -1, city)
}
더 좋은 예 (Codable 모델 기반)
struct User: Decodable {
let name: String
let age: Int?
let city: String?
}
func handle(data: Data) {
do {
let user = try JSONDecoder().decode(User.self, from: data)
print(user.name, user.age ?? -1, user.city ?? "도시 정보 없음")
} catch {
print("JSON decode 실패:", error)
}
}
6. 정리
- Dictionary 서브스크립트는 항상 Optional을 반환한다.
→ 키가 없을 수 있기 때문. - 여기에 !를 붙이면 언제라도 크래시 날 수 있는 지뢰 코드가 된다.
- 안전한 접근 패턴:
- if let / guard let + as?
- ?? 기본값 제공
- 가능하면 Codable 모델 사용하는 방식으로 전환
- 원칙: > 외부 입력(서버, 파일, 사용자 입력 등)에 대해서는 절대 강제 언래핑을 쓰지 않는다.
반응형

