web-dev-qa-db-ja.com

Swiftで、オプションとnilオブジェクト参照の両方を回避するにはどうすればよいですか?

オプションの全体的な理由は、nil/null/noneに割り当てられた変数にヒットすることによるランタイムクラッシュを防ぐためです。したがって、変数をnilにすることはできません。代わりに、SomeまたはNoneとして表すオプションタイプでラップし、ラップを解除してSomeまたはnilの特定のコンテンツを取得できます。

しかし、!を使用して、または暗黙的にアンラップされたオプションを使用して、それらをあちこちでアンラップすると、不完全なコーダーであるため、ランタイムクラッシュの可能性が導入されます。それらを安全にアンラップするためにif letを使用すると、クラッシュを回避できますが、Some内の値を処理するためにif letステートメントのスコープ内で動かなくなり、潜在的なnilケースを処理する必要があります。使用する場合?メソッドを呼び出すために一時的にアンラップするには、完了すると再ラップされ、複数のレイヤー化されたオプションのラッピングの厄介な可能性が導入されます。

だから:私には、必要な場合(たとえば、それらを返すフレームワークメソッドを呼び出すときなど)を除いて、オプションを回避するようにしているように見えます。しかし、オプションを使用していない場合、それはオブジェクト参照がnil以外である必要があることを意味し、何らかの理由で存在すべきでない、または存在しない場合を処理する方法を理解できません。 、オブジェクト参照に割り当てられた値。

私の質問は次のとおりです。nilの必要性を回避するにはどうすればよいですか?別のプログラミングアプローチが必要なようです。 (または、オプションのみを使用することになっていますか?それが私がやっていることである場合、他の言語のようにオブジェクト参照へのnull割り当てを単に持つよりもどのように優れていますか?)

これは主観的な質問である可能性があることはわかっていますが、他にどこに質問すればよいですか?私は議論を荒らしたり騒ぎ立てたりしようとはしていません。もっとSwiftコードを書くとき、正しいアプローチが何であるかを本当に知りたいです。

34
Ivan X

そうです、オプションは苦痛になる可能性があるので、使いすぎてはいけません。しかし、それらはフレームワークを使用するときに対処しなければならないもの以上のものです。これらは、信じられないほど一般的な問題の解決策です。問題がない場合とそうでない場合がある結果を返す呼び出しを処理する方法です。

たとえば、Array.firstメンバーを取り上げます。これは、配列の最初の要素を提供する便利なユーティリティです。 a.firstを呼び出すことができるのに、a[0]を呼び出すことができると便利なのはなぜですか。実行時に配列が空になる可能性があるため、その場合はa[0]が爆発します。もちろん、事前にa.countを確認することもできますが、もう一度確認してください。

a。あなたは忘れるかもしれません

そして

b。その結果、非常に醜いコードになります。

Array.firstは、オプションを返すことでこれに対処します。したがって、配列の最初の要素である値を使用する前に、オプションのラップを解除する必要があります。

さて、if letのブロック内にのみ存在するアンラップされた値に関する問題について。配列数をチェックして、並列コードを想像してください。同じですよね?

if a.count > 0 {
    // use a[0]
}
// outside the block, no guarantee
// a[0] is valid

if let firstElement = a.first {
    // use firstElement
}
// outside the block, you _can't_
// use firstElement

確かに、カウントがゼロの場合は、関数からの早期復帰などを実行できます。これは機能しますが、少しエラーが発生しやすくなります。これを忘れた場合、または実行されなかった条件ステートメントに入れた場合はどうなりますか?基本的には、array.firstでも同じことができます。関数の早い段階でカウントを確認してから、後でarray.first!を実行してください。しかし、その!はあなたへの合図のようなものです。注意してください。あなたは危険なことをしているので、コードが完全に正しくないと申し訳ありません。

オプションは、代替案を少しきれいにするのにも役立ちます。配列が空の場合、値をデフォルトにしたいとします。これの代わりに:

array.count > 0 ? a[0] : somedefault

あなたはこれを書くことができます:

array.first ?? somedefault

これはいくつかの点で優れています。それは重要なことを前に置きます:あなたが望む値は式がどのように始まるかであり、デフォルトが続きます。最初にチェック式でヒットし、次に実際に必要な値、最後にデフォルトでヒットする3項式とは異なります。また、より確実で、タイプミスを回避するのがはるかに簡単であり、そのタイプミスによって実行時の爆発が発生することは不可能です。

別の例を見てみましょう:find関数。これは、値がコレクションにあるかどうかをチェックし、その位置のインデックスを返します。ただし、値がコレクションに存在しない場合があります。他の言語では、終了インデックス(値を指すのではなく、最後の値を1つ超えたもの)を返すことでこれを処理する場合があります。これは「センチネル」値と呼ばれる値です。通常の結果のように見えますが、実際には特別な意味があります。前の例と同様に、使用する前に、結果が終了インデックスと等しくないことを確認する必要があります。しかし、これを行うにはknowする必要があります。 findのドキュメントを調べて、それがどのように機能するかを確認する必要があります。

findがオプションを返す場合、オプションのイディオムを理解すると、当然のことですが、その理由は、明らかな理由で結果が有効でない可能性があるためです。また、上記の安全性に関するすべての同じことが当てはまります。最初にラップを解除する必要があるため、誤って忘れて結果をインデックスとして使用することはできません。

とは言うものの、オプションはチェックしなければならない負担なので、使いすぎる可能性があります。これが、配列の添え字がオプションを返さない理由です。特に、使用しているインデックスが有効であることがわかっている場合(たとえば、配列の有効なインデックス範囲に対するforループ)人々が!を常に使用しているため、コードが煩雑になるのを防ぐことができます。ただし、最初や最後のようなヘルパーメソッドが追加され、最初に配列サイズを確認せずにすばやく操作を実行したいが、安全に実行したいという一般的なケースに対応しています。

(一方、Swift Dictionariesは、無効な添え字を介して定期的にアクセスされることが期待されているため、[key]メソッドはオプションを返します)

さらに良いのは、失敗の可能性を完全に回避できる場合です。たとえば、filterが要素に一致しない場合、オプションのnilは返されません。空の配列を返します。 「まあ、明らかにそうだ」とあなたは言うかもしれません。しかし、実際に空の値を返すべきなのに、配列のような戻り値をオプションにする人がどれほど頻繁にいるかに驚くでしょう。したがって、必要な場合を除いて、オプションを避けるべきだと言うのは完全に正しいです。それは、必要な意味の問題にすぎません。上記の例では、それらが必要であり、代替案に対するより良い解決策であると言えます。

84

または、オプションを使用することになっています。それが私が行っていることである場合、他の言語のようにオブジェクト参照にnullを割り当てるよりも、どうすればよいでしょうか。

計算で「特別な」値を返す必要がある場合は、はい、Swiftではオプションを使用することになっています。それらはexplicit。ポインタがnilである場合を見逃しがちですが、オプションを使用することは非常に困難です(しかし完全に可能です)。

「if let」を使用して安全にアンラップすると、クラッシュを回避できますが、「if let」ステートメントのスコープ内で値を操作できず、潜在的なnilケースを処理する必要があります。

それが特徴です。つまり、それがオプションのタイプの要点です。両方のケース(nilと非nil)を処理する必要があり、それについて明示する必要があります。

同様の概念の例については、Haskellの 多分タイプとモナド を参照してください。 Maybeタイプはオプションタイプとまったく同じです。Maybeモナドを使用すると、空の値を常に手動でチェックしなくても、これらのオプション値を使用して操作を「チェーン」することが非常に簡単になります。

13
zoul

Swift 2以降にはguard letこれはこの質問で言及された問題の多くを解決します。必要に応じて、変数名を再利用することもできます。

func compute(value: Double?) -> Double? {
    guard let value = value  else { return nil }

    // ... `value` is now a regular Double within this function
}

オプションのチェーン?)最初の部分がnilを返すときに式を短絡するには:

let answer = compute(value: 5.5)?.rounded()

そしてnil合体??)nilの代わりにデフォルト値を提供するには:

let answer:Double = compute(value: 5.5) ?? 0

オプションはすべての問題に適切なツールではないが、それらを回避する必要はないという他の投稿にも同意します。使用する if letguard let??など、nil値がどのように処理または渡されるかを表します。

1
Robin Stewart

このように考えてください。オプションの変数が必要な状況ではvar value: Double?、同等のオプションではない変数のペアを使用できます。

var value: Double
var valueExists: Bool

valueを使用する場合は常に、valueExistsかどうかを手動で確認する必要があります。

if valueExists { /* do something */ }
else { /* handle case where value is not valid */ }

通常、デュアル変数を渡してこれらのチェックを手動で行うよりも、オプションを処理するために設計された言語機能を使用する方がはるかに簡単です。また、valueが無効な場合の処理​​をコンパイラに常に通知するようにすると、より安全になります。このような理由で、オプションが発明されました!

1
Robin Stewart