「暗黙的にアンラップされたオプション」を作成する理由は、値があることがわかっているからです。


493

通常の変数または定数だけを作成するのではなく、「暗黙的にアンラップされたオプション」を作成するのはなぜですか?正常にアンラップできることがわかっている場合は、なぜ最初にオプションを作成するのですか?たとえば、これはなぜですか:

let someString: String! = "this is the string"

より便利になるでしょう:

let someString: String = "this is the string"

「オプションが定数または変数が「値なし」を許可されていることを示す」場合でも、「プログラムの構造から、オプションがその値が最初に設定された後は常に値を持つことが明らかである場合があります」、のポイントは何ですかそもそもそれをオプションにしていますか?オプションが常に値を持つことを知っているなら、それはオプションではないのではないですか?

回答:


127

オブジェクトの構築および構成中にnilプロパティを持つ可能性があるが、その後は不変で非nilであるオブジェクトの場合を考慮してください(NSImageは多くの場合、このように扱われますが、その場合でも、場合によっては変化させると便利です)。暗黙的にラップされていないオプションは、安全性の損失が比較的少ない状態で、コードをかなりクリーンアップします(1つの保証が保持されている限り、安全です)。

(編集)ただし、明確にするために:通常のオプションはほとんど常に望ましいです。


459

Implicitly Unwrapped Optionalsの使用例を説明する前に、SwiftのOptionalsとImplicitly Unwrapped Optionalsについて理解しておく必要があります。そうでない場合は、まずオプションに関する私の記事を読むことをお勧めします

暗黙的にアンラップされたオプションを使用する場合

Implicitly Unwrapped Optionalを作成する主な理由は2つあります。nilそれ以外の場合、Swiftコンパイラーは常にオプションを明示的にアンラップするように強制するため、アクセスされない変数を定義する必要があります。

1.初期化中に定義できない定数

すべてのメンバー定数は、初期化が完了するまでに値を持つ必要があります。場合によっては、定数は初期化中に正しい値で初期化できないことがありますが、アクセスされる前に値があることが保証されます。

オプション変数を使用すると、このオプションが自動的に初期化されnil、最終的に含まれる値が不変になるため、この問題を回避できます。しかし、確かにnilではないことがわかっている変数を常にアンラップするのは、骨の折れる作業です。暗黙的にアンラップされたオプションは、オプションと同じ利点を達成しますが、すべての場所で明示的にアンラップする必要がないという利点があります。

この良い例は、ビューが読み込まれるまで、UIViewサブクラスでメンバー変数を初期化できない場合です。

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

ここでは、ビューが読み込まれるまでボタンの元の幅を計算できませんが、ビューのawakeFromNib他のメソッド(初期化以外)の前に呼び出されることがわかっています。値をクラス全体で無意味に明示的にアンラップするのではなく、暗黙的にアンラップしたオプションとして宣言できます。

2.アプリが変数から回復できない場合 nil

これは非常にまれなはずですが、変数nilにアクセスしたときにアプリが実行を継続できない場合、わざわざ変数をテストするのは時間の無駄になりますnil。通常、アプリの実行を継続するために絶対的に真でなければならない条件がある場合は、を使用しassertます。暗黙的にアンラップされたオプションには、nilのアサートが組み込まれています。それでも、オプションのラップを解除し、それがnilの場合はより記述的なアサートを使用することはしばしば良いことです。

暗黙的にアンラップされたオプションを使用しない場合

1.遅延計算されるメンバー変数

nilであってはならないが、初期化時に正しい値に設定できないメンバー変数がある場合があります。1つの解決策は、暗黙的にアンラップされたオプションを使用することですが、より良い方法は遅延変数を使用することです。

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

現在、メンバー変数contentsは、最初にアクセスされるまで初期化されません。これにより、クラスは初期値を計算する前に正しい状態に入る機会が与えられます。

注:これは上記の1と矛盾するように見えるかもしれません。ただし、重要な違いがあります。buttonOriginalWidth上記は、プロパティにアクセスする前に、ボタンを変更し、誰もが幅防ぐためのviewDidLoad中に設定する必要があります。

2.その他の場所

ほとんどの場合、Implicitly Unwrapped Optionalsは回避する必要があります。誤って使用すると、アプリケーションのアクセス中にアプリケーション全体がクラッシュするためですnil。変数をnilにできるかどうか不明な場合は、常にデフォルトで通常のオプションを使用します。決してnil確実ではない変数をアンラップしても、それほど害はありません。


4
この回答はベータ5で更新する必要がありますif someOptional。今後は使用できません。
サンタクロース

2
@SantaClaus hasValueはOptionalで正しく定義されています。私はのセマンティクス好みhasValueのものにします!= nilnil他の言語を使ったことがない新しいプログラマーにとっては、ずっと理解しやすいと思います。hasValueはよりもはるかに論理的ですnil
drewag 2014

2
hasValueベータ6からプルされたように見えますが、Ashはそれを元に戻しました... github.com/AshFurrow/hasValue
Chris Wagner

1
@newacct Objcイニシャライザの戻り値の型に関しては、暗黙の暗黙的にラップされていないオプションです。「非オプション」を使用するために記述した動作は、暗黙的にアンラップされたオプションが行うこととまったく同じです(アクセスされるまで失敗しません)。強制的にアンラップすることでプログラムを早期に失敗させることに関して、オプションではないものを使用することをお勧めしますが、これが常に可能であるとは限りません。
drewag 2014

1
@ファイル番号。何があっても、Objective-Cではポインタとして表示されます(オプション、暗黙的にラップ解除、またはオプションではない場合)。
drewag

56

暗黙的にアンラップされたオプションは、実際にはオプションである必要がある場合に、プロパティを非オプションとして提示するのに役立ちます。これは多くの場合、それぞれが他のオブジェクトへの参照を必要とする2つの関連オブジェクト間の「結び目」を結ぶために必要です。どちらの参照も実際にはオプションではない場合に意味がありますが、ペアが初期化されている間は、どちらかをnilにする必要があります。

例えば:

// These classes are buddies that never go anywhere without each other
class B {
    var name : String
    weak var myBuddyA : A!
    init(name : String) {
        self.name = name
    }
}

class A {
    var name : String
    var myBuddyB : B
    init(name : String) {
        self.name = name
        myBuddyB = B(name:"\(name)'s buddy B")
        myBuddyB.myBuddyA = self
    }
}

var a = A(name:"Big A")
println(a.myBuddyB.name)   // prints "Big A's buddy B"

どのBインスタンスも常に有効なmyBuddyA参照を持つ必要があるので、ユーザーにそれをオプションとして扱わせたくありませんBが、A参照する前にを構築できるように、インスタンスをオプションにする必要があります。

しかしながら!この種の相互参照要件は、結合が密で設計が不十分であることを示していることがよくあります。暗黙的にアンラップされたオプションに依存している場合は、相互依存関係を排除するためにリファクタリングを検討する必要があります。


7
私は、彼らがこの言語機能を作成した理由の一つだと思う@IBOutlet
Jiaaro

11
「HOWEVER」の警告の+1。それは常に本当であるとは限らないかもしれませんが、それは確かに注意すべきことです。
JMD 2014年

4
AとBの間に強い参照サイクルがあります。暗黙的にアンラップされたオプションは、弱い参照を作成しません。myByddyAまたはmyBuddyBを弱い(おそらくmyBuddyA)と宣言する必要があります
drewag

6
この答えが間違っていて危険な誤解を招く理由をさらに明確にするために:暗黙的にアンラップされたオプションは、メモリ管理とまったく関係がなく、保持サイクルを妨げません。ただし、暗黙的にアンラップされたオプションは、双方向参照を設定するために説明されている状況では依然として役立ちます。したがって、weak宣言を追加して「強力な保持サイクルを作成せずに」削除するだけです
drewag '26 / 07/26

1
@drewag:その通りです-私は回答を編集して保持サイクルを削除しました。私は後方参照を弱くするつもりでしたが、それは私の心を滑らせたと思います。
n8gray 2014

37

暗黙的にラップされていないオプションは、既存のCocoaフレームワークおよびその規約との相互運用が必要なハイブリッド環境での作業をより快適にすると同時に、Swiftコンパイラーによって実施されるnullポインターなしでより安全なプログラミングパラダイムへの段階的な移行を可能にする実用的な妥協策です。

Swiftブックの「基本」の章の「暗黙的にアンラップされたオプションセクションでは、次のように述べています。

暗黙的にアンラップされたオプションは、オプションの値が最初に定義された直後にそのオプションの値が存在することが確認され、その後のすべての時点で確実に存在すると想定できる場合に役立ちます。所有されていない参照と暗黙的にラップされていないオプションのプロパティで説明されているように、Swift で暗黙的にラップされていないオプションの主な用途は、クラスの初期化中です。

暗黙的にアンラップされたオプションは、使用されるたびにオプションが自動的にアンラップされる許可を与えると考えることができます。使用するたびにオプション名の後に感嘆符を付けるのではなく、宣言するときにオプションのタイプの後に感嘆符を置きます。

これは、プロパティのnilネスが使用規則によって確立され、クラスの初期化中にコンパイラーによって強制できないユースケースに帰着します。たとえばUIViewController、NIBまたはストーリーボードから初期化されるプロパティです。初期化は個別のフェーズに分割されviewDidLoad()ますが、その後、プロパティは一般に存在すると想定できます。それ以外の場合は、コンパイラーを満足させるために、 強制アンラップオプションのバインディング、 またはオプションのチェーン を使用して、コードの主な目的を不明瞭にする必要がありました。

Swiftブックの上の部分は、自動参照カウントの参照しています

ただし、3つ目のシナリオでは、両方のプロパティに常に値を設定し、nil初期化が完了するとどちらのプロパティも設定しないようにする必要があります。このシナリオでは、1つのクラスの所有されていないプロパティを、他のクラスの暗黙的にラップされていないオプションのプロパティと組み合わせると便利です。

これにより、参照サイクルを回避しながら、初期化が完了すると、両方のプロパティに直接(オプションのアンラップなしで)アクセスできます。

これはガベージコレクションされた言語ではないという癖に帰着します。そのため、プログラマーとして保持サイクルの解除はプログラマーにあり、暗黙的にアンラップされたオプションはこの癖を隠すためのツールです。

これは、「コードで暗黙的にアンラップされたオプションをいつ使用するか」をカバーしています。質問。アプリケーション開発者は、Objective-Cで記述されたライブラリのメソッドシグネチャでそれらに遭遇しますが、これにはオプションの型を表現する機能がありません。

ココアとObjective-C、セクションでスウィフトを使用してゼロでの作業

Objective-Cはオブジェクトが非nilであることを保証しないため、Swiftは、インポートされたObjective-C APIで引数型と戻り型のすべてのクラスをオプションにします。Objective-Cオブジェクトを使用する前に、それが欠落していないことを確認する必要があります。

いくつかのケースでは、あなたはあるかもしれない絶対に Objective-Cのメソッドやプロパティが返されないこと、特定のnilオブジェクト参照を。この特別なシナリオのオブジェクトを扱いやすくするために、Swiftはオブジェクトタイプを暗黙的にアンラップされたオプションとしてインポートします。暗黙的にアンラップされたオプションタイプには、オプションタイプのすべての安全機能が含まれています。さらに、チェックせずに値に直接アクセスできますnilまたは自分で開封します。安全にアンラップせずにこの種類のオプションの型の値にアクセスすると、暗黙的にアンラップされたオプションが値がないかどうかをチェックします。値がない場合、ランタイムエラーが発生します。結果として、値が欠落していないことが確実でない限り、暗黙的にアンラップされたオプションを自分で常にチェックしてアンラップする必要があります。

...そしてここに横たわっていた りゅう


この完全な回答に感謝します。Implicitly Unwrapped Optionalsをいつ使用し、いつ標準変数で十分か、簡単なチェックリストを思いつくでしょうか?
Hairgami_Master

@Hairgami_Master私はリストと具体的な例を使用して自分の回答を追加しました
drewag

18

1行(または複数行)の単純な例では、オプションの動作を十分にカバーしていません。そうです、変数を宣言してすぐに値を提供する場合、オプションには意味がありません。

これまでに見た中で最も良いケースは、オブジェクトの初期化の後に行われるセットアップであり、その後、そのセットアップに従うために「保証された」使用が続きます。

class MyViewController: UIViewController {

    var screenSize: CGSize?

    override func viewDidLoad {
        super.viewDidLoad()
        screenSize = view.frame.size
    }

    @IBAction printSize(sender: UIButton) {
        println("Screen size: \(screenSize!)")
    }
}

printSizeビューが読み込まれた後に呼び出されることがわかっています。これは、そのビュー内のコントロールにフックされたアクションメソッドであり、それ以外の場合は呼び出さないようにしました。したがって、を使用して、オプションのチェック/バインディングをいくつか保存することができます!。Swiftはその保証を認識できないため(少なくともAppleが停止の問題を解決するまで)、それが存在することをコンパイラーに伝えます。

ただし、これはタイプセーフをある程度壊します。暗黙的にラップされていないオプションがある場所は、「保証」が常に成立するとは限らない場合にアプリがクラッシュする可能性がある場所なので、控えめに使用する機能です。その上、!いつも使うことはあなたが叫んでいるように聞こえ、誰もそれが好きではありません。


screenSizeをCGSize(高さ:0、幅:0)に初期化して、変数にアクセスするたびに変数を叫ぶ必要がないようにするのはなぜですか。
Martin Gordon

CGSizeZero実際の使用では、センチネル値として適切な場合があるため、サイズは最良の例ではなかったかもしれません。しかし、実際にはゼロである可能性があるnibからロードされたサイズがある場合はどうなりますか?次にCGSizeZero、番兵として使用しても、値が設定されていないこととゼロに設定されていることを区別できません。さらに、これはnibからロードされた他のタイプ(またはその後のどこかinit)にも同様に適用されます:文字列、サブビューへの参照など
rickster

2
関数型言語のオプションのポイントの一部は、センチネル値を持たないことです。あなたは価値があるか持っていないかのどちらかです。欠損値を示す値がある場合はありません。
ウェインタナー

4
OPの質問を誤解していると思います。OPは、オプションの一般的なケースについてではなく、具体的には、暗黙的にアンラップされたオプション(つまりlet foo? = 42、ではなくlet foo! = 42)の必要性/使用について尋ねているのではありません。これでは対処できません。(これはオプションについての適切な回答かもしれませんが、別の/関連する動物である暗黙的にアンラップされたオプションについてではありません。)
JMD

15

AppleはSwiftプログラミング言語の優れた例を示しています-> 自動参照カウント -> クラスインスタンス間の強い参照サイクルの解決 ->所有されていない参照と暗黙的にラップされていないオプションプロパティ

class Country {
    let name: String
    var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

の初期化子Cityは、の初期化子内から呼び出されますCountry。ただし、の初期化子Countryは渡すことができませんselfCity新しいまで初期化子Countryで説明したようにインスタンスが完全に初期化され、二相の初期化

この要件に対処するには、のcapitalCityプロパティをCountry暗黙的にアンラップされたオプションのプロパティとして宣言します。


この回答で言及されているチュートリアルはこちらです。
フランクリンYu

3

暗黙的なオプションの理論的根拠は、最初に強制アンラップの理論的根拠を見ることで説明しやすくなります。

!を使用して、オプション(暗黙的かどうかにかかわらず)の強制アンラップ 演算子は、コードにバグがなく、オプションが既にアンラップされる値を持っていることを意味します。なし!演算子、あなたはおそらくオプションのバインディングでアサートするでしょう:

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

それはそれほど良くない

println("\(value!)")

暗黙的なオプションは、可能なすべてのフローで、ラップされていないときに常に値を期待するオプションがあることを表現できます。だから、それはあなたを助けることに一歩前進します-を書くという要件を緩和することによって!毎回アンラップし、フローに関する想定が間違っている場合でもランタイムがエラーを確実に発生するようにします。


1
@newacct:コードを通過する可能性のあるすべてのフローのnon-nilは、親(クラス/構造体)の初期化から割り当て解除によるnon-nilと同じではありません。インターフェイスビルダーは古典的な例です(ただし、他の多くの遅延初期化パターンがあります)。クラスがnibからのみ使用される場合、アウトレット変数は設定されinitません(実装しない場合もあります)が、再保証の後に設定されるようにawakeFromNib/ viewDidLoad
リクスター2016年

3

確かにわかっている場合はnil、の代わりにオプションから値が返されます。暗黙的にアンラップされたオプションは、これらの値をオプションから直接キャッチするために使用し、オプション以外からは取得できません。

//Optional string with a value
let optionalString: String? = "This is an optional String"

//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!

//Declaration of a non Optional String
let nonOptionalString: String

//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString

//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString

これが使用の違いです

let someString : String! そして let someString : String


1
これはOPの質問には答えません。OP は、暗黙的にアンラップされたオプションが何であるかを知っています。
フランクリンユー

0

Optional多くの初心者を混乱させるこの構成の悪い名前だと思います。

他の言語(KotlinやC#など)では、という用語を使用Nullableしており、これを理解するのが非常に簡単になっています。

Nullableこのタイプの変数にnull値を割り当てることができることを意味します。したがって、それNullable<SomeClassType>がnullの場合は、nullを割り当てるSomeClassTypeことができます。これがSwiftの動作方法です。

なぜそれらを使うのですか?まあ、ヌルが必要な場合があります。それが理由です。たとえば、クラスにフィールドが必要であることがわかっているが、そのクラスのインスタンスを作成しているときにそれを何にも割り当てることができないが、後でフィールドを割り当てる場合などです。ここでは例を示しません。私はこれを私の2セントを与えるために書いています。

ところで、KotlinやC#などの他の言語でこれがどのように機能するかを確認することをお勧めします。

Kotlinのこの機能を説明するリンクは次のとおりです。https//kotlinlang.org/docs/reference/null-safety.html

JavaやScalaなどの他の言語にはOptionalsがありますが、動作は異なります。Optional JavaおよびScalaの型はすべてデフォルトでnull可能であるため、Swiftのとはます。

全体として、この機能はNullableSwiftで命名されたはずであり、Optional...


0

Implicitly Unwrapped OptionalOptionalプログラマーが変数をアンラップすることを強制しない構文シュガーです。これは、初期化できず、nilでないことtwo-phase initialization process意味する変数に使用できます。この変数はそれ自体が非nilとして動作しますが、実際にはオプションの変数です。良い例は-Interface Builderのアウトレットです。

Optional 通常は好ましい

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.