Manjusaka

Manjusaka

Swift 3.0 に新たに追加された安全機能の概要

Swift がリリースされた後、Swift の開発者たちは、安全性とオプショナル型が Swift の最も重要な特徴の一つであると強調してきました。彼らは nil の表現メカニズムを提供し、nil になる可能性のあるインスタンスに対して明確な構文を使用することを要求しています。

オプショナル型には主に以下の 2 種類があります:

  1. Optional
  2. ImplicitlyUnwrappedOptional

最初のアプローチは安全な方法です:オプショナル型の変数を解体して基礎値にアクセスすることを要求します。2 つ目のアプローチは安全でない方法です:オプショナル型の変数を解体せずにその基礎値に直接アクセスできます。例えば、変数の値が nil のときに ImplicitlyUnwrappedOptional を使用すると、いくつかの例外が発生する可能性があります。

以下にこの問題に関する例を示します:


    let x: Int! = nil
    print(x) // クラッシュ! `x` は nil です!

Swift 3.0 では、Apple は ImplicitlyUnwrappedOptional の実装を改善し、以前よりも安全になりました。ここで私たちは、Apple が Swift 3.0ImplicitlyUnwrappedOptional に対してどのような改善を行ったのか、Swift をより安全にするために何をしたのかを尋ねずにはいられません。答えは、Apple がコンパイラにおいて ImplicitlyUnwrappedOptional の型推論を最適化したことにあります。

Swift 2.x での使用方法#

ここでの変化を理解するために、例を見てみましょう。

    struct Person {
        let firstName: String
        let lastName: String

        init!(firstName: String, lastName: String) {
            guard !firstName.isEmpty && !lastName.isEmpty else {
                return nil
            }
            self.firstName = firstName
            self.lastName = lastName
        }
    }

ここでは、初期化メソッドに欠陥のある構造体 Person を作成しました。もし初期化時にインスタンスに first namelast name の値を提供しない場合、初期化は失敗します。

ここで init!(firstName: String, lastName: String) では、? ではなく ! を使用して初期化を行っています。Swift 3.0 とは異なり、Swift 2.x では init! を使用して ImplicitlyUnwrappedOptional を利用していました。使用している Swift のバージョンに関わらず、init! の使用には注意が必要です。一般的に、生成されたインスタンスが nil になることによって発生する例外を許容できる場合にのみ、init! を使用するべきです。対応するインスタンスが nil の場合、init! を使用するとプログラムがクラッシュします。

.* では、この初期化メソッドは ImplicitlyUnwrappedOptional<Person> を生成します。初期化が失敗した場合、すべての Person に基づくインスタンスは例外を引き起こします。

例えば、Swift 2.x では、以下のコードは実行時にクラッシュします。

    // Swift 2.x

    let nilPerson = Person(firstName: "", lastName: "Mathias")
    nilPerson.firstName // クラッシュ!

注意すべきは、初期化子に暗黙的なアンラップがあるため、nilPerson を正常に使用するために型バインディング(訳者注 1: optional binding )やオプショナルチェイニング(訳者注 2: optional chaining )を使用する必要がないことです。

Swift 3.0 での新しい姿勢#

Swift 3.0 では、事態が少し変わりました。init!! は初期化が失敗する可能性があることを示し、成功した場合には生成されたインスタンスは強制的に暗黙的にアンラップされます。Swift 2.x とは異なり、init! が生成するインスタンスは optional であり、ImplicitlyUnwrappedOptional ではありません。これは、異なる基礎値に対してインスタンスを型バインディングまたはオプショナルチェイニング処理する必要があることを意味します。


    // Swift 3.0

    let nilPerson = Person(firstName: "", lastName: "Mathias")
    nilPerson?.firstName

上記の例では、nilPersonOptional<Person> 型のインスタンスです。これは、内部の値に正常にアクセスするためには nilPerson をアンラップする必要があることを意味します。この場合、手動でのアンラップは非常に良い選択です。

安全な型宣言#

この変化は混乱を招くかもしれません。なぜ init! を使用した初期化が Optional 型のインスタンスを生成するのでしょうか? init!!ImplicitlyUnwrappedOptional を生成することを示しているのではないでしょうか?

答えは、安全性と宣言の依存関係にあります。上記のコード( let nilPerson = Person(firstName: "", lastName: "Mathias") )では、コンパイラが nilPerson の型を推論します。

Swift 2.x では、コンパイラは nilPersonImplicitlyUnwrappedOptional<Person> として処理します。理論的には、私たちはこのコンパイル方式に慣れており、ある程度の理にかなっています。要するに、Swift 2.x では、ImplicitlyUnwrappedOptional を使用するには init! を利用してインスタンスを初期化する必要がありました。

しかし、ある意味で、このアプローチは非常に安全ではありません。正直なところ、私たちは nilPersonImplicitlyUnwrappedOptional インスタンスであるべきだという明確な意図を持っていません。将来的にコンパイラが不安全な型情報を推論し、プログラムの実行に偏差を引き起こす場合、あなたたちも責任を負うことになります。

Swift 3.0 はこのような安全性の問題を解決する方法として、明示的に ImplicitlyUnwrappedOptional を宣言しない限り、ImplicitlyUnwrappedOptionaloptional として処理します。

ImplicitlyUnwrappedOptional のインスタンス渡しの制限#

このアプローチの巧妙な点は、暗黙的なアンラップされた optional インスタンスの渡しを制限することです。前述の Person のコードを参考にし、以前の Swift 2.x でのいくつかのアプローチを考えてみましょう:


    // Swift 2.x

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt.firstName // `matt` は `ImplicitlyUnwrappedOptional<person>` であり、直接 `firstName` にアクセスできます
    let anotherMatt = matt // `anotherMatt` も `ImplicitlyUnwrappedOptional<person>` です

anotherMattmatt と同じ型のインスタンスです。このような状況はあまり理想的ではないことを予想しているかもしれません。コード内で ImplicitlyUnwrappedOptional のインスタンスが渡されています。この新たに生成された不安全なコードに対しては、特に注意が必要です。

例えば、上記のコードで非同期操作を行った場合、状況はどうなるでしょうか?

    // Swift 2.x

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt.firstName // `matt` は `ImplicitlyUnwrappedOptional<person>` であり、直接 `firstName` にアクセスできます
    ... // 何かが起こる; 時間が経過する; コードが実行される; `matt` が nil に設定される
    let anotherMatt = matt // `anotherMatt` も同じ型: `ImplicitlyUnwrappedOptional<person>`

上記の例では、anotherMatt は値が nil のインスタンスであり、これは基礎値に直接アクセスする操作がクラッシュを引き起こすことを意味します。このタイプのアクセスは、正確には ImplicitlyUnwrappedOptional が推奨する方法です。では、anotherMattOptional<Person> に変更した場合、状況は改善されるでしょうか?

Swift 3.0 で同じコードを試してみましょう。


    // Swift 3.0

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt?.firstName // `matt` は `Optional<person>` です
    let anotherMatt = matt // `anotherMatt` も `Optional<person>` です

もし私たちが生成したのが ImplicitlyUnwrappedOptional 型のインスタンスであることを明示的に宣言しなければ、コンパイラはデフォルトでより安全な Optional を使用します。

型推論は安全であるべき#

この変化の最大の利点は、コンパイラの型推論が私たちのコードの安全性を低下させないことです。必要な場合に、私たちが選択した不安全な方法については、明示的な宣言を行う必要があります。これにより、コンパイラは自動的な判断を行わなくなります。

時には、私たちが本当に ImplicitlyUnwrappedOptional 型のインスタンスを使用する必要がある場合、単に明示的に宣言する必要があります。

    // Swift 3.0

    let runningWithScissors: Person! = Person(firstName: "Edward", lastName: "") // Person! を明示的に宣言する必要があります!
    let safeAgain = runningWithScissors // ここでの型は何ですか?

runningWithScissors は値が nil のインスタンスです。なぜなら、初期化時に lastName に空の文字列を与えたからです。

注意すべきは、私たちが宣言した runningWithScissors インスタンスが ImplicitlyUnwrappedOptional<Person> のインスタンスであることです。Swift 3.0 では、SwiftOptionalImplicitlyUnwrappedOptional を同時に使用することを許可しています。ただし、明示的に宣言する必要があり、コンパイラに対して ImplicitlyUnwrappedOptional を使用していることを伝える必要があります。

幸運なことに、コンパイラはもはや safeAgainImplicitlyUnwrappedOptional インスタンスとして自動的に処理しません。代わりに、コンパイラは safeAgain 変数を Optional インスタンスとして処理します。このプロセスにおいて、Swift 3.0 は不安全なインスタンスの伝播を効果的に制限しました。

いくつかの言いたいこと#

ImplicitlyUnwrappedOptional の変更は、私たちが通常 macOS または iOS 上で Objective-C で書かれた API を操作する際に起こる理由かもしれません。これらの API では、特定の状況下で返される値が nil である可能性があり、Swift にとってはこの状況は安全ではありません。

したがって、Swift はこのような不安全な状況が発生するのを避けています。ImplicitlyUnwrappedOptional に対する Swift 開発者の改善に感謝します。私たちは今、堅牢なコードを書くことが非常に簡単になりました。もしかしたら、将来的には ImplicitlyUnwrappedOptional が完全に私たちの視界から消えるかもしれません。

最後に#

この分野についてもっと知りたい場合は、こちらの this proposal から有用な情報を得ることができます。issue では、この提案の著者の考えを得ることができ、具体的な変更を通じて詳細を理解することができます。また、そこには関連するコミュニティの議論へのリンクもあります。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。