「致命的なエラー: オプション値のアンラップ中に予期せず nil が見つかりました」とはどういう意味ですか? 質問する

「致命的なエラー: オプション値のアンラップ中に予期せず nil が見つかりました」とはどういう意味ですか? 質問する

Swift プログラムがクラッシュしEXC_BAD_INSTRUCTION、次のようなエラーが発生します。このエラーの意味は何ですか? また、どのように修正すればよいですか?

致命的なエラー: オプション値のアンラップ中に予期せず nil が見つかりました

または

致命的なエラー: 暗黙的にオプション値をアンラップしているときに予期せず nil が見つかりました


この投稿は、「予期せずnilが見つかった」問題に対する回答を収集し、それらが散らばって見つけにくくならないようにすることを目的としています。ご自由に独自の回答を追加してください。編集既存のウィキの回答。

ベストアンサー1

背景: オプションとは何ですか?

Swiftでは、Optional<Wrapped>オプションタイプ: 元の ("ラップされた") 型の任意の値を含めることも、値をまったく含めないことも (特殊な値 ) できます。オプションの値は、使用する前にラップを解除するnil必要があります。

オプションはジェネリックタイプOptional<Int>は異なる型であることを意味しますOptional<String>。内部の型<>はラップ型と呼ばれます。内部的には、Optionalは列挙型2 つのケースがあります:.some(Wrapped)および.none。ここ.noneで は と同等ですnil

オプションは、名前付き型を使用して宣言することOptional<T>も、(最も一般的には) サフィックス付きの省略形として宣言することもできます?

var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int?  // `nil` is the default when no value is provided
var aVerboseOptionalInt: Optional<Int>  // equivalent to `Int?`

anOptionalInt = nil // now this variable contains nil instead of an integer

オプションは、コードを書く際に仮定を表現するためのシンプルかつ強力なツールです。コンパイラはこの情報を使用して、間違いを防ぐことができます。Swift プログラミング言語:

Swift は型安全言語です。つまり、この言語は、コードで使用できる値の型を明確にするのに役立ちます。コードの一部に が必要な場合String、型安全により、Int誤って を渡すことが防止されます。同様に、型安全により、オプションでない を必要とするコードにオプションを誤って渡すことが防止されます。StringString型安全により、開発プロセスのできるだけ早い段階でエラーを検出して修正できます。

他のプログラミング言語にもジェネリックがあるオプションの種類: 例えば、多分Haskellでは、オプションRustで、そしてオプションC++17 では。

オプション型を持たないプログラミング言語では、特定の「センチネル」値有効な値が存在しないことを示すためによく使用されます。たとえば、Objective-Cでは、nilヌルポインタ) は、オブジェクトが存在しないことを示します。 などのプリミティブ型の場合int、ヌル ポインターは使用できないため、別の変数 ( や など) または指定されたセンチネル値 ( または など) が必要になりますvalue: IntisValid: Boolこれら-1INT_MINアプローチは、センチネル値のチェックやチェックを忘れやすいため、エラーが発生しやすくなります。 また、特定の値がセンチネルとして選択された場合、その値は有効なisValid値として扱えなくなります。

Swift などのオプション型は、Optional特別な個別のnil値を導入することで (そのため、センチネル値を指定する必要がありません)、また強力な型システムを活用して、必要に応じて nil をチェックすることを忘れないようにコンパイラーが支援することで、これらの問題を解決します。


致命的なエラー: Optional 値のアンラップ中に予期せず nil が見つかりました」というエラーが表示されるのはなぜですか?

オプションの値にアクセスするには (値がある場合)、それをアンラップする必要があります。オプションの値は、安全に、または強制的にアンラップできます。オプションを強制的にアンラップし、値がない場合は上記のメッセージが表示されてプログラムがクラッシュします。

Xcode はコード行を強調表示してクラッシュを表示します。問題はこの行で発生します。

衝突した線路

このクラッシュは、次の 2 種類の強制アンラップによって発生する可能性があります。

1. 明示的なフォースアンラッピング

これは、オプションの演算子を使用して実行されます!。例:

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

致命的なエラー: オプション値のアンラップ中に予期せず nil が見つかりました

anOptionalStringここで示されているようにnil、強制的にアンラップする行でクラッシュが発生します。

2. 暗黙的にアンラップされたオプション

これらは、型の後の!ではなくで定義されます。?

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

これらのオプションには値が含まれていると想定されます。したがって、暗黙的にアンラップされたオプションにアクセスするたびに、自動的に強制的にアンラップされます。値が含まれていない場合はクラッシュします。

print(optionalDouble) // <- CRASH

致命的なエラー:暗黙的にオプション値をアンラップしているときに予期せず nil が見つかりました

どの変数がクラッシュの原因になったのかを突き止めるには、クリックしたままにして定義を表示し、オプションの型を見つけるとよいでしょう。

特に IBOutlet は、通常、暗黙的にアンラップされたオプションです。これは、xib またはストーリーボードが初期化後に実行時にアウトレットをリンクするためです。したがって、アウトレットがロードされる前にアクセスしていないことを確認する必要があります。また、ストーリーボード/xib ファイルで接続が正しいことを確認する必要があります。そうでない場合、値はnil実行時に設定され、暗黙的にアンラップされたときにクラッシュします。接続を修正するときは、アウトレットを定義するコード行を削除してから、再接続してみてください。


Optional を強制的にアンラップする必要があるのはどのような場合ですか?

明示的なフォースアンラッピング

一般的なルールとして、演算子を使用してオプションを明示的に強制的にアンラップしないでください!。 の使用が許容される場合もあります!が、オプションに値が含まれていることが 100% 確実な場合にのみ使用してください。

オプションに値が含まれていることは事実であるため、強制アンラップを使用できる場合もありますが、代わりにそのオプションを安全にアンラップできない場所は1 つもありません。

暗黙的にアンラップされたオプション

これらの変数は、コードの後半まで割り当てを延期できるように設計されています。変数にアクセスする前に、変数に値があることを確認するのはユーザーの責任です。ただし、強制アンラップが伴うため、nil の割り当てが有効であるにもかかわらず、値が nil 以外であると想定されるため、本質的に安全ではありません。

暗黙的にアンラップされたオプションは最後の手段としてのみ使用してください。遅延変数、または提供デフォルト値変数の場合 – 暗黙的にアンラップされたオプションを使用する代わりに、そうする必要があります。

しかし、暗黙的にアンラップされたオプションが有益なシナリオはほとんどない、以下に挙げるさまざまな方法で安全に開封することができますが、常に十分な注意を払って使用する必要があります。


Optional を安全に処理するにはどうすればよいでしょうか?

オプションに値が含まれているかどうかを確認する最も簡単な方法は、それを と比較することですnil

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

ただし、オプションを使用する場合、99.9% の場合、オプションに含まれる値 (値が含まれている場合) にアクセスする必要があります。これを行うには、Optional Binding を使用できます。

オプションバインディング

Optional Binding を使用すると、optional に値が含まれているかどうかを確認でき、ラップされていない値を新しい変数または定数に割り当てることができます。バインド後に新しい変数の値を変更する必要があるかどうかに応じて、構文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")
}

もう 1 つの便利なトリックは、値をアンラップした後、コンマを使用して値の特定の条件をチェックできることです。

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)!")

ガードは、複数のオプションを同時にアンラップしたり、where句を使用したりといった、if ステートメントがサポートしていたのと同じ便利なトリックもサポートします。

if ステートメントを使用するか guard ステートメントを使用するかは、将来のコードでオプションに値を含める必要があるかどうかによって完全に決まります。

Nil 合体演算子

Nil 合体演算子は、三項条件演算子は、主にオプションを非オプションに変換するように設計されています。 という構文がありa ?? b、 はaオプション型で、bと同じ型ですa(ただし、通常は非オプションです)。

a基本的には、「値が含まれている場合は、それをアンラップします。含まれていない場合は、代わりに戻ります」と言うことがbできます。たとえば、次のように使用できます。

let number = anOptionalInt ?? 0

これにより、 型numberの定数が定義され、値が含まれている場合は の値が含まれ、そうでない場合は が含まれます。IntanOptionalInt0

これは単に次の略語です:

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

オプションの連鎖

使用できますオプションの連鎖オプションのメソッドを呼び出したり、プロパティにアクセスしたりするには、変数名に接尾辞 a を付けて?使用します。

たとえば、fooオプションのインスタンス型の変数 があるとしますFoo

var foo : Foo?

foo何も返さないメソッドを呼び出したい場合は、次のようにするだけです。

foo?.doSomethingInteresting()

foo値が含まれている場合、このメソッドがその値に対して呼び出されます。値が含まれていない場合は何も起こりません。コードはそのまま実行を続けます。

nil(これはObjective-Cでメッセージを送信するのと似た動作です)

したがって、これはメソッドの呼び出しだけでなく、プロパティの設定にも使用できます。例:

foo?.bar = Bar()

foo繰り返しますが、の場合、ここでは何も悪いことは起こりません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

mapそしてflatMap

mapオプションではあまり使われていない機能は、 および関数を使用できることですflatMap。これらを使用すると、オプションではない変換をオプション変数に適用できます。オプションに値がある場合は、特定の変換を適用できます。値がない場合は のままになりますnil

たとえば、オプションの文字列があるとします。

let anOptionalString:String?

mapこれに関数を適用することで、stringByAppendingString関数を使用してそれを別の文字列に連結することができます。

stringByAppendingStringはオプションではない文字列引数を取るため、オプションの文字列を直接入力することはできません。ただし、 を使用すると、に値がある場合に を使用できるようmapになります。stringByAppendingStringanOptionalString

例えば:

var anOptionalString:String? = "bar"

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

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

ただし、anOptionalStringに値がない場合、mapが返されますnil。例:

var anOptionalString:String?

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

print(anOptionalString) // nil

flatMapは と同様に動作しますが、クロージャ本体内から別のmapオプションを返すことができます。つまり、オプションではない入力を必要とするプロセスにオプションを入力し、オプション自体を出力できます。

try!

Swiftのエラー処理システムは、やってみる、やってみる、キャッチする:

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」する方法も提供されていますtry!

let result = try! someThrowingFunc()

この投稿で説明した概念はここでも適用されます。エラーがスローされると、アプリケーションはクラッシュします。

try!コンテキスト内でその結果が決して失敗しないことが証明できる場合にのみ使用してください。これは非常にまれです。

ほとんどの場合、完全な Do-Try-Catch システムを使用しますがtry?、エラーの処理が重要でないまれなケースではオプションの を使用します。


リソース

おすすめ記事