Swift のオプション値とは何ですか? 質問する

Swift のオプション値とは何ですか? 質問する

からAppleのドキュメント:

ifと をlet一緒に使用して、欠落している可能性のある値を操作できます。これらの値はオプションとして表されます。オプションの値には、値が含まれているか、値が欠落していることを示す値が含まれています。値をオプションとしてマークするには、値の種類の後にnil疑問符 ( ) を記述します。?

オプションの値を使用する理由は何でしょうか?

ベストアンサー1

Swift のオプショナルは、値を保持するか、値を保持しないかのどちらかの型です。オプショナルは、?任意の型に を追加して記述します。

var name: String? = "Bertie"

Optional は (Generic とともに) Swift の最も理解しにくい概念の 1 つです。その書き方や使い方のせいで、誤解されやすいものです。上記の Optional と通常の String の作成を比較してみましょう。

var name: String = "Bertie" // No "?" after String

構文から見ると、オプションの文字列は通常の文字列と非常によく似ているように見えます。しかし、そうではありません。オプションの文字列は、何らかの「オプション」設定がオンになっている文字列ではありません。文字列の特別な種類でもありません。文字列とオプションの文字列は完全に異なる型です。

知っておくべき最も重要なことは、optional はコンテナの一種であるということです。optional String は、String を含む可能性のあるコンテナです。optional Int は、Int を含む可能性のあるコンテナです。optional を一種の小包と考えてください。optional を開く (optional の用語では「アンラップ」する) まで、何かが含まれているのか、何も含まれていないのかはわかりません。

見ることができますオプションの実装方法Swift 標準ライブラリでは、任意の Swift ファイルに「Optional」と入力して ⌘ キーを押しながらクリックすることで定義できます。定義の重要な部分は次のとおりです。

enum Optional<Wrapped> {
    case none
    case some(Wrapped)
}

Optional は であり、または のenum2 つのケースのいずれかになります。 の場合、関連付けられた値があり、上記の例では「Hello」になります。optional は、関連付けられた値に型を与えるためにジェネリックを使用します。optional String の型は ではなく、、より正確には です。.none.some.someStringStringOptionalOptional<String>

Swift がオプショナルで行っていることはすべて、コードの読み書きをよりスムーズにするための魔法です。残念ながら、これによって実際の動作方法がわかりにくくなっています。後でいくつかのトリックを説明します。

注:オプション変数について多く説明しますが、オプション定数を作成しても問題ありません。作成される型を理解しやすくするために、すべての変数に型をマークしていますが、独自のコードではマークする必要はありません。


オプションの作成方法

オプションを作成するには、?ラップするタイプの後に を追加します。任意のタイプをオプションにすることができ、独自のカスタム タイプもオプションにすることができます。タイプと の間にスペースを入れることはできません?

var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)

// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}

オプションの使用

オプションを比較して、nil値があるかどうかを確認できます。

var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
    print("There is a name")
}
if name == nil { // Could also use an "else"
    print("Name has no value")
}

これは少し紛らわしいです。これは、optional が 1 つまたは別のものであることを意味します。nil か "Bob" のどちらかです。これは正しくありません。optional は他の何かに変換されません。nil と比較するのは、コードを読みやすくするためのトリックです。optional が nil に等しい場合、これは enum が現在 に設定されていることを意味します.none


オプションのみnilにできます

オプションでない変数を nil に設定しようとすると、エラーが発生します。

var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'

オプショナルを通常の Swift 変数の補足として見ることもできます。オプショナルは、値を持つことが保証されている変数の対極です。Swift は曖昧さを嫌う慎重な言語です。ほとんどの変数は非オプショナルとして定義されていますが、これが不可能な場合もあります。たとえば、キャッシュまたはネットワークから画像をロードするビュー コントローラを想像してください。ビュー コントローラが作成された時点では、その画像があるかもしれませんし、ないかもしれません。画像変数の値を保証する方法はありません。この場合、それをオプショナルにする必要があります。nil画像が取得されると、オプショナルに値が設定されます。

オプションを使用すると、プログラマーの意図が明らかになります。どのオブジェクトも nil になる可能性がある Objective-C と比較すると、Swift では、値が欠落している可能性がある場合と、値が存在することが保証されている場合を明確にする必要があります。


オプションを使用するには、それを「アンラップ」します

オプションは、String実際の の代わりに使用できませんString。オプション内でラップされた値を使用するには、ラップを解除する必要があります。オプションをラップ解除する最も簡単な方法は、!オプション名の後に を追加することです。これは「強制ラップ解除」と呼ばれます。これは、オプション内の値を (元の型として) 返しますが、オプションが の場合はnil、実行時にクラッシュを引き起こします。ラップ解除する前に、値があることを確認する必要があります。

var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")

name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.

オプションの確認と使用

アンラップしてオプションを使用する前に常に nil をチェックする必要があるため、これは一般的なパターンです。

var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
    let unwrappedMealPreference: String = mealPreference!
    print("Meal: \(unwrappedMealPreference)") // or do something useful
}

このパターンでは、値が存在するかどうかを確認し、存在することが確実になったら、強制的に一時定数にアンラップして使用します。これは非常に一般的なことなので、Swift では「if let」を使用したショートカットを提供しています。これは「オプショナル バインディング」と呼ばれます。

var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
    print("Meal: \(unwrappedMealPreference)") 
}

letこれにより、if の中括弧内のみのスコープを持つ一時的な定数 (またはに置き換えた場合は変数var) が作成されます。「unwrappedMealPreference」や「realMealPreference」のような名前を使用するのは面倒なので、Swift では元の変数名を再利用して、括弧のスコープ内に一時的な変数を作成できます。

var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
    print("Meal: \(mealPreference)") // separate from the other mealPreference
}

別の変数が使用されていることを示すコードを次に示します。

var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
    print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
    mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"

オプショナル バインディングは、オプショナルが nil に等しいかどうかをチェックすることで機能します。等しくない場合は、オプショナルを指定された定数にアンラップし、ブロックを実行します。Xcode 8.3 以降 (Swift 3.1) では、次のようにオプショナルを印刷しようとすると、無意味な警告が発生します。debugDescriptionこれを黙らせるには、オプショナルを使用します。

print("\(mealPreference.debugDescription)")

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

オプションには 2 つの使用例があります。

  1. 失敗する可能性のあるもの(何かを期待していたが、何も得られなかった)
  2. 今は何もないが、将来何かになるかもしれないもの(逆もまた同様)

具体的な例をいくつか挙げます。

  • クラスのmiddleNameように、存在したり存在しなかったりするプロパティspousePerson
  • 配列内の一致を検索するなど、値を返すか何も返さないメソッド
  • 結果を返すか、エラーを取得して何も返さないメソッド。たとえば、ファイルの内容を読み取ろうとしたが(通常はファイルのデータを返す)、ファイルが存在しないなどです。
  • デリゲートプロパティは必ずしも設定する必要はなく、通常は初期化後に設定されます。
  • クラス内のプロパティの場合。それが指すものはいつでもweak設定できますnil
  • メモリを再利用するために解放する必要がある可能性のある大きなリソース
  • 値が設定されたことを知る方法が必要な場合(まだロードされていないデータ > データ)、別のdataLoadedを使用する代わりにBoolean

Objective-C にはオプションは存在しませんが、nil を返すという同等の概念があります。オブジェクトを返すことができるメソッドは、代わりに nil を返すことができます。これは、「有効なオブジェクトが存在しない」という意味と解釈され、何かが間違っていることを示すためによく使用されます。これは Objective-C オブジェクトでのみ機能し、プリミティブや基本的な C 型 (列挙型、構造体) では機能しません。Objective-C には、これらの値が存在しないことを表す特殊な型がよくありました (NSNotFound実際にはNSIntegerMaxkCLLocationCoordinate2DInvalid無効な座標を表す 、-1または負の値も使用されます)。コーダーはこれらの特殊な値について知っておく必要があるため、各ケースについてドキュメント化して学習する必要があります。メソッドが をnilパラメーターとして取れない場合は、これをドキュメント化する必要があります。Objective-C では、nilすべてのオブジェクトがポインターとして定義されているのと同じように はポインターでしたが、nil特定の (ゼロ) アドレスを指していました。Swift では、 はnil特定の型が存在しないことを意味するリテラルです。


比較するとnil

以前は、任意のオプションを として使用できましたBoolean

let leatherTrim: CarExtras? = nil
if leatherTrim {
    price = price + 1000
}

Swift の最近のバージョンでは、 を使用する必要がありますleatherTrim != nil。これはなぜでしょうか? 問題は、 がBooleanオプションでラップされる可能性があることです。Boolean次のようになっている場合:

var ambiguous: Boolean? = false

これには 2 種類の「false」があり、1 つは値がない場合、もう 1 つは値があるが値が である場合ですfalse。Swift は曖昧さを嫌うため、常にオプションを に対してチェックする必要がありますnil

オプショナルの意味が何なのか疑問に思うかもしれませんBoolean。他のオプショナルと同様に、.none状態は値がまだ不明であることを示す可能性があります。ネットワーク呼び出しの反対側に、ポーリングに時間がかかる何かがある可能性があります。オプショナルブール値は「3値ブール値


Swiftのトリック

Swift では、optional を機能させるためにいくつかのトリックを使用します。普通に見える 3 行のオプション コードを検討してください。

var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }

これらの行はいずれもコンパイルされません。

  • 最初の行は、2つの異なる型の文字列リテラルを使用してオプションの文字列を設定します。これが文字列であったとしても、String型は異なります。
  • 2行目はオプションの文字列をnilに設定し、2つの異なる型
  • 3行目はオプションの文字列とnilを比較します。2つの異なる型です。

これらの行を機能させるオプションの実装の詳細をいくつか説明します。


オプションの作成

オプションを作成するために を使用すること?は、Swift コンパイラによって有効化される構文糖です。長い方法で実行したい場合は、次のようにオプションを作成できます。

var name: Optional<String> = Optional("Bob")

Optionalこれは、 の最初の初期化子を呼び出しpublic init(_ some: Wrapped)、括弧内で使用される型からオプションの関連型を推論します。

オプションを作成して設定するさらに長い方法:

var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")

オプションを設定するnil

初期値のないオプションを作成することも、初期値を持つオプションを作成することもできますnil(どちらも結果は同じです)。

var name: String?
var name: String? = nil

オプションを と等しくすることはnil、プロトコルExpressibleByNilLiteral(以前は と呼ばれていましたNilLiteralConvertible) によって有効になります。オプションは、Optionalの 2 番目の初期化子である で作成されますpublic init(nilLiteral: ())。ドキュメントには、ExpressibleByNilLiteralコード内の nil の意味が変わるため、オプション以外に を使用すべきではないと書かれていますが、次のように使用することは可能です。

class Clint: ExpressibleByNilLiteral {
    var name: String?
    required init(nilLiteral: ()) {
        name = "The Man with No Name"
    }
}

let clint: Clint = nil // Would normally give an error
print("\(clint.name)")

同じプロトコルを使用して、すでに作成されたオプションを に設定できますnil。推奨されませんが、 nil リテ​​ラル初期化子を直接使用することもできます。

var name: Optional<String> = Optional(nilLiteral: ())

オプションとの比較nil

Optional は、定義で確認できる 2 つの特別な "==" および "!=" 演算子を定義しますOptional。最初の演算==子を使用すると、任意の option が nil と等しいかどうかを確認できます。.none に設定された 2 つの異なる option は、関連付けられた型が同じであれば常に等しくなります。nil と比較する場合、Swift はバックグラウンドで同じ関連付けられた型の option を作成し、.none に設定して比較に使用します。

// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
    print("tuxedoRequired is nil")
}

2 番目の==演算子を使用すると、2 つのオプションを比較できます。両方とも同じ型である必要があり、その型はEquatable(通常の "==" 演算子を使用して比較できるプロトコル) に準拠している必要があります。Swift は (おそらく) 2 つの値をアンラップし、直接比較します。オプションの 1 つまたは両方が である場合も処理します.none。リテラルとの比較の違いに注意してくださいnil

Equatableさらに、任意の型をその型をラップするオプションのものと比較することもできます。

let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
    print("It's a match!") // Prints "It's a match!"
}

Swiftは、比較の前に非オプションをオプションとしてラップします。リテラルでも機能します(if 23 == numberFromString {

演算子は2つあると言いましたが、実際には比較の左側に==置くことができる3つ目の演算子があります。nil

if nil == name { ... }

命名オプション

Swift には、オプション型と非オプション型を区別して命名する規則はありません。オプション型であることを示すために名前に何かを追加することは避け (「optionalMiddleName」や「possibleNumberAsString」など)、宣言でオプション型であることを示すようにします。オプション型からの値を保持するために名前を付ける場合は、これが難しくなります。「middleName」という名前は、それが String 型であることを意味します。そのため、そこから String 値を抽出すると、「actualMiddleName」や「unwrappedMiddleName」や「realMiddleName」のような名前になることがよくあります。これを回避するには、オプション バインディングを使用して変数名を再利用します。


公式の定義

からSwift プログラミング言語の「基礎」:

Swift では、値がない場合を処理するオプショナル型も導入されています。オプショナル型は、「値があり、それが x に等しい」か「値がまったくない」かのいずれかを表します。オプショナル型は、Objective-C でポインターに nil を使用するのと似ていますが、クラスだけでなく、どの型でも機能します。オプショナル型は、Objective-C の nil ポインターよりも安全で表現力に富んでおり、Swift の最も強力な機能の多くで中核をなしています。

Optional は、Swift が型安全な言語であることを示す例です。Swift を使用すると、コードで使用できる値の型を明確にすることができます。コードの一部で String が想定されている場合、型安全性により、誤って Int を渡すことが防止されます。これにより、開発プロセスのできるだけ早い段階でエラーを検出して修正できます。


最後に、1899 年に書かれたオプションに関する詩を紹介します。

昨日階段で
出会った男はそこにはいなかった
今日もまた彼はい
なかった彼がいなくなってくれればいいのに

アンティゴニッシュ


その他のリソース:

おすすめ記事