“치명적 오류 : 선택적 값을 래핑 해제하는 동안 예기치 않게 nil이 발견되었습니다”라는 의미는 무엇입니까? 예기치 않게 nil이 발견되었습니다.

내 Swift 프로그램이 충돌 EXC_BAD_INSTRUCTION하고 다음과 유사한 오류 중 하나가 발생합니다. 이 오류는 무엇을 의미하며 어떻게 수정합니까?

치명적 오류 : 선택적 값을 래핑 해제하는 동안 예기치 않게 nil이 발견되었습니다.

또는

치명적 오류 : 암시 적으로 선택적 값을 래핑 해제하는 동안 예기치 않게 nil이 발견되었습니다.


이 게시물은 “예기치 않은 nil 발견”문제에 대한 답변을 수집하여 흩어져 있고 찾기 어렵도록하기위한 것입니다. 자유롭게 답변을 추가하거나 기존 위키 답변을 편집 하십시오.



답변

이 답변은 커뮤니티 위키 입니다. 더 나아질 수 있다고 생각되면 자유롭게 편집하십시오 !

배경 : 선택 사항은 무엇입니까?

Swift에서 값은 모든 종류의 값을 포함하거나 전혀 값을 포함 할 수 Optional없는 일반 유형 입니다.

다른 많은 프로그래밍 언어에서 특정 “센티넬”값은 종종 값 부족 을 나타내는 데 사용됩니다 . 예를 들어 Objective-C에서 nil( null 포인터 )는 객체가 없음을 나타냅니다. 그러나 이것은 프리미티브 유형으로 작업 할 때 더 까다로워집니다 -1. 정수 INT_MIN또는 다른 정수 가 없음을 나타내는 데 사용해야 합니까? “정수 없음”을 의미하도록 특정 값을 선택하면 더 이상 유효한 값 으로 처리 할 수 ​​없습니다 .

Swift는 형식에 안전한 언어이므로 코드를 사용하여 코드를 사용할 수있는 값의 유형을 명확하게 알 수 있습니다. 코드의 일부에 문자열이 필요한 경우 형식 안전으로 인해 실수로 Int를 전달할 수 없습니다.

Swift에서는 모든 유형 을 선택적으로 만들 수 있습니다 . 선택적 값은 원래 유형의 값 또는 특수 값을 취할 수 있습니다 nil.

옵션은 ?유형에 접미사 로 정의됩니다 .

var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int?    // `nil` is the default when no value is provided

옵션에 값이 없다는 것은 다음과 같이 표시됩니다 nil.

anOptionalInt = nil

(이것은 Objective-C nil와 동일하지 않습니다 nil. Objective-C에서는 nil유효한 객체 포인터 가 없습니다 .Swift에서는 Optionals가 객체 / 참조 유형으로 제한되지 않습니다. Optional은 Haskell의 Maybe 와 유사하게 동작 합니다.)


왜 ” 치명적 오류 : 선택적 값을 풀 때 예기치 않게 nil이 발견되었습니다 “가 발생하는 이유는 무엇 입니까?

옵션의 값에 액세스하려면 (옵션이있는 경우) 랩핑해제 해야합니다. 선택적인 값은 안전하거나 강제로 풀 수 있습니다. 옵션을 강제로 풀고 값 이 없으면 위의 메시지와 함께 프로그램이 중단됩니다.

Xcode는 코드 줄을 강조 표시하여 충돌을 보여줍니다. 이 줄에서 문제가 발생합니다.

부서진 선

이 충돌은 두 가지 다른 종류의 강제 전개 해제에서 발생할 수 있습니다.

1. 명시 적 포스 풀기

이것은 !옵션으로 운영자 와 함께 수행됩니다 . 예를 들면 다음과 같습니다.

let anOptionalString: String?
print(anOptionalString!) // <- CRASH

치명적 오류 : 선택적 값을 래핑 해제하는 동안 예기치 않게 nil이 발견되었습니다.

대로 anOptionalString입니다 nil여기에, 당신은 당신이 그것을 unwrap를 강제 라인에 충돌을 얻을 것이다.

2. 암시 적으로 랩핑되지 않은 옵션

이들은 유형 !뒤가 아니라 으로 정의됩니다 ?.

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used

이러한 옵션은 값을 포함한다고 가정합니다. 따라서 암시 적으로 랩핑되지 않은 옵션에 액세스 할 때마다 자동으로 랩핑 해제됩니다. 값을 포함하지 않으면 충돌이 발생합니다.

print(optionalDouble) // <- CRASH

치명적 오류 : 암시 적 으로 선택적 값을 래핑 해제하는 동안 예기치 않게 nil이 발견되었습니다.

충돌을 일으킨 변수를 해결하기 위해 클릭하여 정의를 표시 할 수 있습니다 (선택적 유형을 찾을 수있는 위치).

특히 IBOutlet은 일반적으로 암시 적으로 래핑되지 않은 옵션입니다. 초기화 xib 또는 스토리 보드가 런타임에 콘센트를 연결하기 때문 입니다. 따라서 콘센트가로드되기 전에 콘센트에 액세스하지 않는지 확인해야합니다. 또한 스토리 보드 / xib 파일에서 연결이 올바른지 확인해야합니다. 그렇지 않으면 값이 nil런타임시이므로 암시 적으로 랩핑되지 않은 경우 충돌합니다. . 연결을 수정할 때 콘센트를 정의하는 코드 줄을 삭제 한 다음 다시 연결하십시오.


언제 옵션을 풀어야합니까?

명백한 힘 풀기

일반적으로 !연산자를 사용 하여 선택 사항을 명시 적으로 강제로 풀어야합니다 . 사용 !이 허용되는 경우가있을 수 있지만 옵션에 값이 포함되어 있는지 100 % 확신 할 경우에만 사용해야합니다.

이 동안 수 있습니다 당신이 알고 당신이, 힘 풀기 사용할 수있는 기회가 될 사실 옵션에 값이 포함되어 있음 -이없는 하나의 장소 어디 수없는 안전하게 포장을 벗긴 것을 선택하는 대신.

암시 적으로 래핑되지 않은 옵션

이 변수는 나중에 코드에서 할당을 연기 할 수 있도록 설계되었습니다. 액세스하기 전에 가치가 있는지 확인하는 것은 귀하의 책임입니다. 그러나 강제 래핑 해제와 관련되어 있기 때문에 nil을 할당하는 것이 유효하더라도 값이 nil이 아닌 것으로 가정 하기 때문에 여전히 본질적으로 안전하지 않습니다.

최후의 수단 으로 암시 적으로 래핑되지 않은 옵션 만 사용해야합니다 . 당신이 사용할 수있는 경우 게으른 변수를 하거나 제공하는 기본값을 변수에 대한 – 당신은 너무 대신 암시 적으로 풀어 옵션을 사용해야한다.

그러나 암시 적으로 래핑되지 않은 옵션이 유리한 몇 가지 시나리오가 있으며 아래 나열된대로 안전하게 래핑 해제하는 다양한 방법을 계속 사용할 수 있습니다. 그러나 항상 신중하게 사용해야합니다.


옵션을 어떻게 안전하게 다룰 수 있습니까?

선택 사항에 값이 포함되어 있는지 확인하는 가장 간단한 방법은 값과 비교하는 것 nil입니다.

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}

그러나 옵션을 사용하여 작업하는 경우 시간의 99.9 %가 실제로 포함 된 값에 액세스하려는 경우 실제로 값에 액세스하려고합니다. 이를 위해 Optional Binding을 사용할 수 있습니다 .

선택적 바인딩

선택적 바인딩을 사용하면 옵션에 값이 포함되어 있는지 확인할 수 있으며 래핑되지 않은 값을 새 변수 또는 상수에 할당 할 수 있습니다. 바인딩 후 새 변수의 값을 수정해야하는지에 따라 구문 if let x = anOptional {...}또는을 사용합니다 if var x = anOptional {...}.

예를 들면 다음과 같습니다.

if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}

이것이하는 일은 먼저 선택 사항에 값이 포함되어 있는지 확인하는 것입니다. 이 경우 수행 한 후 ‘미개봉’값은 새 변수 (에 할당 number이 아닌 선택 사항 인 것처럼 다음 자유롭게 사용할 수 있습니다 -). 옵션 에 값 이 없으면 else 절이 호출됩니다.

선택적 바인딩의 장점은 여러 옵션을 동시에 풀 수 있다는 것입니다. 문장을 쉼표로 구분하면됩니다. 모든 선택 사항이 랩핑 해제되면 명령문이 성공합니다.

var anOptionalInt : Int?
var anOptionalString : String?

if let number = anOptionalInt, let text = anOptionalString {
    print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
    print("One or more of the optionals don’t contain a value")
}

또 다른 깔끔한 요령은 쉼표를 사용하여 값을 푼 후 값의 특정 조건을 확인할 수 있다는 것입니다.

if let number = anOptionalInt, number > 0 {
    print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}

if 문 내에서 선택적 바인딩을 사용하는 유일한 방법은 명령문 범위 내에서 래핑되지 않은 값에만 액세스 할 수 있다는 것입니다. 명령문 범위 밖에서 값에 액세스해야하는 경우 guard 문을 사용할 수 있습니다 .

가드 문은 당신이 성공을위한 조건을 정의 할 수 있습니다 – 그 조건이 충족되는 경우 현재 범위는 계속 실행됩니다. 그것들은 구문으로 정의됩니다 guard condition else {...}.

따라서 선택적 바인딩과 함께 사용하려면 다음을 수행하십시오.

guard let number = anOptionalInt else {
    return
}

가드 본문 내 에서 현재 실행중인 코드의 범위를 종료하려면 제어 전송 문 중 하나 를 사용해야합니다 .

anOptionalInt값 이 포함되어 있으면 래핑되지 않고 새 number상수에 할당됩니다 . 가드 이후 의 코드 는 계속 실행됩니다. 값을 포함하지 않는 경우 – 가드는 괄호 안에있는 코드를 실행하여 제어권을 양도하므로 즉시 코드가 실행되지 않습니다.

(우리는 미래의 코드가 있습니다 알고 가드 문에 대한 실제 깔끔한 것은 미개봉 값이 문을 다음 코드에서 사용하는 사용할 수 있습니다입니다 옵션에 값이있는 경우 실행). 이것은 여러 if 문을 중첩하여 생성 된 ‘피라미드의 운명’ 을 제거하는 데 좋습니다 .

예를 들면 다음과 같습니다.

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")

또한 Guard는 여러 개의 옵션을 동시에 풀고 where절을 사용하는 등 if 문이 지원하는 것과 동일한 깔끔한 트릭을 지원합니다 .

당신이 경우 또는 가드 문을 사용하든 완전히 향후 코드 여부에 따라 필요로 하는 값을 포함하도록 선택합니다.

무 응집 연산자

무기 호 합체 운영자 의 멋진 속기 버전입니다 삼항 조건 연산자 주로 비 선택적 항목에 선택적 항목을 변환하도록 설계되었습니다. 여기에는 구문이 있습니다 a ?? b. 여기서 a선택적 유형이며 b다음과 같은 유형입니다 a(보통 비 선택적임).

본질적으로“ a값 이 포함되어 있으면 포장을 풉니 다. 그렇지 않으면 b대신 반환하십시오 .” 예를 들어 다음과 같이 사용할 수 있습니다.

let number = anOptionalInt ?? 0

이것은 유형 의 number상수를 정의합니다.이 상수 Int는 값을 포함하거나 값을 anOptionalInt포함하는 경우 값을 포함합니다 0.

다음과 같은 속기입니다.

let number = anOptionalInt != nil ? anOptionalInt! : 0

옵션 체인

옵션 체인 을 사용 하여 메서드를 호출하거나 옵션의 속성에 액세스 할 수 있습니다. 변수 이름을 사용할 ?때 접미사를 붙이면 됩니다.

예를 들어 foo선택적 Foo인스턴스 유형의 변수가 있다고 가정 해보십시오.

var foo : Foo?

foo아무것도 반환하지 않는 메서드를 호출하려면 간단히 다음을 수행하십시오.

foo?.doSomethingInteresting()

foo값이 포함되어 있으면 이 메소드가 호출됩니다. 그렇지 않으면 나쁜 일이 발생하지 않습니다. 코드는 계속 실행됩니다.

(이것은 nilObjective-C에서 메시지를 보내는 것과 유사합니다 )

따라서 이것은 호출 메소드뿐만 아니라 특성을 설정하는 데에도 사용될 수 있습니다. 예를 들면 다음과 같습니다.

foo?.bar = Bar()

또한 foois 인 경우에도 나쁜 일은 발생하지 않습니다 nil. 코드는 단순히 계속 실행됩니다.

선택적 체인을 통해 할 수있는 또 하나의 깔끔한 트릭은 속성 설정 또는 메서드 호출이 성공적인지 확인하는 것입니다. 반환 값을와 비교하여이 작업을 수행 할 수 있습니다 nil.

(이것은 아무것도 반환하지 않는 메소드가 Void?아닌 선택적 값이 반환 되기 때문 Void입니다)

예를 들면 다음과 같습니다.

if (foo?.bar = Bar()) != nil {
    print("bar was set successfully")
} else {
    print("bar wasn’t set successfully")
}

그러나 속성에 액세스하거나 값을 반환하는 메서드를 호출하려고하면 상황이 조금 더 까다로워집니다. foo선택 사항 이므로 반환 된 항목도 선택 사항입니다. 이를 처리하기 위해, 위의 방법 중 하나를 사용하여 리턴 된 옵션을 랩 해제 foo하거나 메소드에 액세스하거나 값을 리턴하는 메소드를 호출하기 전에 자체 랩핑을 해제 할 수 있습니다.

또한 이름에서 알 수 있듯이 이러한 진술을 ‘연결’할 수 있습니다. 경우에 것을이 수단 foo선택 특성이 baz특성을 가지고 qux: 당신이 다음을 쓸 수 있습니다 –

let optionalQux = foo?.baz?.qux

때문에 다시, foo그리고 baz선택 사항 값에서 반환 qux에 관계없이 항상 여부의 선택이 될 것 qux자체는 선택 사항입니다.

mapflatMap

옵션과 함께 자주 사용되지 않는 기능 은 mapflatMap기능 을 사용하는 기능입니다. 이를 통해 선택적 변수에 비 선택적 변환을 적용 할 수 있습니다. 옵션에 값이 있으면 주어진 변환을 적용 할 수 있습니다. 값이 없으면 그대로 유지 nil됩니다.

예를 들어, 선택적 문자열이 있다고 가정 해 봅시다.

let anOptionalString:String?

map함수를 적용함으로써 – 우리는 stringByAppendingString함수를 다른 문자열에 연결하기 위해 사용할 수 있습니다 .

때문에 stringByAppendingString비 선택적 문자열 인수를, 우리는 직접 입력 우리의 선택적 문자열을 할 수 없습니다. 그러나을 사용하면 값이 있으면 mapallow stringByAppendingString를 사용할 수 있습니다 anOptionalString.

예를 들면 다음과 같습니다.

var anOptionalString:String? = "bar"

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // Optional("foobar")

그러나 anOptionalStringmap이 없으면를 반환 nil합니다. 예를 들면 다음과 같습니다.

var anOptionalString:String?

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // nil

flatMap클로저 본체 내에서 다른 옵션 map을 반환 할 수 있다는 점을 제외하고 와 비슷하게 작동합니다 . 이는 선택 사항이 아닌 입력이 필요한 프로세스에 선택 사항을 입력 할 수 있지만 선택 사항 자체를 출력 할 수 있음을 의미합니다.

try!

Swift의 오류 처리 시스템은 Do-Try-Catch 와 함께 안전하게 사용할 수 있습니다 .

do {
    let result = try someThrowingFunc() 
} catch {
    print(error)
}

경우 someThrowingFunc()오류가 발생합니다, 오류가 안전하게에 잡힐 것 catch블록.

error당신은에서 볼 일정한 catch블록은 우리가 선언되지 않은 -가 자동으로 생성 된 것 catch.

error자신 을 선언 할 수도 있습니다 . 예를 들어 유용한 형식으로 캐스트 할 수 있다는 장점이 있습니다.

do {
    let result = try someThrowingFunc()    
} catch let error as NSError {
    print(error.debugDescription)
}

try이 방법을 사용 하면 던지는 함수에서 발생하는 오류를 시도, 포착 및 처리 할 수 ​​있습니다.

try?오류를 흡수하는 것도 있습니다 .

if let result = try? someThrowingFunc() {
    // cool
} else {
    // handle the failure, but there's no error information available
}

그러나 Swift의 오류 처리 시스템은 try!다음 과 같이 “강제 시도”하는 방법을 제공합니다 .

let result = try! someThrowingFunc()

이 게시물에 설명 된 개념도 여기에 적용됩니다. 오류가 발생하면 응용 프로그램이 중단됩니다.

try!결과가 컨텍스트에서 절대 실패하지 않는다는 것을 증명할 수있는 경우 에만 사용해야 합니다. 이는 매우 드.니다.

대부분의 try?경우 오류 처리가 중요하지 않은 경우에는 완전한 Do-Try-Catch 시스템과 선택적인 시스템을 사용 하게됩니다.


자원


답변