[Swift/iOS] Fatal error: Unexpectedly found nil while unwrapping an Optional value 분석하기!
앱을 만들다 보면 여러 에러가 발생하곤 하는데, 이럴 땐 구글링 해보고 바로 적용해서 에러를 해결했다. 사실 이렇게 하면 왜 이런 에러가 발생했는지 알지 못할 뿐만 아니라 다음에 또 에러가 발생할 수 있다...! 이를 방지하기 위함 + 멋진 iOS 개발자가 되기 위해 정리하면서 학습하기로 했다🔥🔥
Unexpectedly found nil while unwrapping an Optional value
옵셔널 값을 벗기는 도중에 예상하지 못한 nil이 발견되었다 라는 메세지다.
우선, Optional 개념에 대해 다시 생각해보자. Swift는 사용하는 값의 타입을 명료하게 해주는 type-safe 언어다. 만약 String을 사용하려고 할 때, type safety는 실수로 그것을 Int에 넘기지 않도록 해 준다. 즉, Optional String을 non-Optional String에 넘기지 않게 해준다.
Optional은 두 가지 값을 가질 수 있다.
- original value (Wrapped value)
- no value (nil)
이 optional 값을 사용하려면 꼭 unwrapped 해야 한다.
iOS 앱을 만들면 사용자로부터 입력, 서버로부터 받는 데이터 등 여러 값들을 받을 수 있는데 이 값들이 nil일 수도 있다. nil인데 wrapped value처럼 사용할 수 없지 않은가..! 이를 방지하기 위해서 꼭 체크를 해줘야 한다.
"Fatal error: Unexpectedly found nil while unwrapping an Optional value"가 왜 나왔을까?
위에서 말했듯이 optional value에 접근하기 위해서는 그것을 unwrap 해야 한다!
optional value는 강제로 unwrap 할 수도 있다.
만약 강제로 unwrapping 했는데, 값이 없다면! 위의 메시지를 출력한다.
이런 충돌 에러는 2가지 경우의 강제 unwrapping에서 발생한다.
1. 암시적 강제 unwrapping
let optional: String?
print(optional!) //crash error
위의 코드를 보면 optional 변수는 Optional <String> 타입이고, 값이 들어있지 않다.
즉, nil인 상태인데 이를 강제 unwrapping 해서 값을 출력하라고 하니까 crash error가 발생한 것!
2. 명시적 unwrapped optional
var optional: Double!
이 변수는 꼭 값을 가질 것이라고 가정하는 것이다. 그러므로 이 unwrapped optional에 언제든 접근하면, 자동으로 unwrapped 된다.
그런데, 값이 없으면 당근 crash!
런타임 동안 이 값이 nil이 되는 경우에도 crash가 발생한다.
그럼 언제 옵셔널을 unwrap 해야 할까?
규칙에 따르면,! 를 사용해서 강제 unwrap 하면 안 된다. 정말 100퍼센트 값이 있다고 확신할 수 있는 경우에만 사용해야 한다.
100퍼센트 확신은 할 수 없는 상태에서 안전하게 unwrap 하는 방법을 알아보자.
1. Optional Binding
optional 변수가 값을 가지고 있는지 체크해주는 것이다.
if let num = optional {
print("contains a value")
} else {
print("nil")
}
optional binding을 사용하면 여러 개를 동시에 unwrap 할 수도 있다. , 를 사용해서 각 statements를 분리하면 된다.
var OptionalInt : Int?
var OptionalString : String?
if let num = OptionalInt, let text = OptionalString {
print("OptionalInt contains a value: \(number). And so does OptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
If문이 아니라 guard문을 사용해서 할 수도 있다.
guard let num = optional else { return }
[내가 겪은 에러! - 이 글을 쓰게 된 이유...]
내 문제는 배열을 선언하고 그 값을 모두 nil이 아닌 상태로 만들었다는 것..! -> 즉, nil이 생길 일이 없다. 그런데 발생한 Unexpectedly found nil while unwrapping an Optional value 대체 무슨 일일까... 어디서 nil이 생겼다는 것일까
struct DataModel: Identifiable{
var id = UUID()
var title : String
var link : String
var imageURL : String?
var domain: String
init(title: String, link: String, imageURL: String, domain: String){
self.title = title
self.link = link
self.imageURL = imageURL
self.domain = domain
}
}
struct SubHomeView: View {
@State private var datas = [
DataModel(title: "Twitter", link: "https://www.twitter.com", imageURL: "sample", domain: "twitter.com"),
DataModel(title: "Developer", link: "https://www.developer.apple.com", imageURL: "developer", domain: "developer.apple.com"),
DataModel(title: "Goole(Korea)", link: "https://www.google.co.kr", imageURL: "", domain: "google.co.kr"),
DataModel(title: "Apple(Korea)", link: "https://www.apple.com", imageURL: "iOS", domain: "apple.com")
]
//something
ForEach($datas) { data in
if let urlString = data.link { //옵셔널 바인딩 -> urlString에 link 값 넣기
let url = URL(string: "\(urlString)") //여기서 url = nil 🚨
if let url = url { //⭐️ URL은 옵셔널 타입이므로 꼭 if-let(guard)문으로 옵셔널 바인딩
Link(destination: url, label:{
PageView(data: data)
.padding(4)
})
.foregroundColor(.black)
}
}
}
urlString에는 data의 link(String) 값이 들어간다.
이 부분이 옵셔널 바인딩으로 data.link가 nil이 아니면(값이 존재하면) {}를 진행하는데, data의 link는 옵셔널 값이기 때문에 if let에서 옵셔널을 벗겨서 urlString에 link 값을 넣는다.
let url = URL(string: "\(urlString)") 코드를 실행하면 urlString이 nil이 될 일이 없는데 아래와 같이 nil이 들어간다.
혹시나 해서 URL(string: "https://www.apple.com")과 같이 url 문자열을 넣어줬더니 제대로 작동한다. 대체 왜... 그렇다면 urlString의 문제일 것 같다.
여기서 URL(string: urlString)이 아니라 URL(string: "\(urlString)")으로 한 것은 인자로 String을 넣어야 하는데 urlString은 Binding <String>이기 때문이라는 에러 메세지가 발생했기 때문에 문자열안에 넣어줬다. ☠️(여기가 문제!)
더 파보자!! ⚒
breakpoint을 사용해서 무슨 일인지 코드를 살펴보았다. if let 문으로 벗겨진 urlString은 Binding<String> 타입이 된다. 여기서 Binding<String> 타입인 것이 문제가 아닐까?
URL(string)에는 String 타입이 들어가야 한다. Binding <String?>이 아니라! Binding은 wrapped property로, wrappedValue라는 값을 감싸고 있다. 여기서 urlString은 Binding으로 감싸져 있고 이 값(wrappedValue)을 보면 되지 않을까? 그래서 아래처럼 변경해줬더니!
ForEach($datas) { data in
if let urlString = data.link {
let url = URL(string: urlString.wrappedValue) //Binding<String>에서 감싸인 wrappedValue == url
if let url = url {
Link(destination: url, label:{
PageView(data: data)
.padding(4)
})
.foregroundColor(.black)
}
}
}
성공했다!
📌 Conclusion
이 에러를 통해서 꽤 많은 것을 깨닫게 되었는데,
1. 기본에 충실하자 → optional 공부 다시 했다.
2. breakpoint를 사용하면 어떤 부분에서 에러가 발생했는지 알 수 있다
3. apple document에 들어가면 뭐든 나오게 되어있다.
생각보다 간단한 에러였지만 꽤나 오랜 시간 머리 싸매고 했다. 잘 정리해두고, 다신 이런 실수 없도록!
출처
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
My Swift program is crashing with EXC_BAD_INSTRUCTION and one of the following similar errors. What does this error mean, and how do I fix it? Fatal error: Unexpectedly found nil while unwrappin...
stackoverflow.com