Manjusaka

Manjusaka

Swift 3.0 における安全機能の新たな普及

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

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

  1. Optional
  2. ImplicitlyUnwrappedOptional

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

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


    let x: Int! = nil
    print(x) // Crash! `x` is 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 // Crash!

注意すべきは、初期化子において暗黙的な解包が存在するため、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` is `ImplicitlyUnwrappedOptional<person>`; we can access `firstName` directly</person>
    let anotherMatt = matt // `anotherMatt` is also `ImplicitlyUnwrappedOptional<person>`</person>

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

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

    // Swift 2.x

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt.firstName // `matt` is `ImplicitlyUnwrappedOptional<person>`, and so we can access `firstName` directly</person>
    ... // 何かが起こる; 時間が経過する; コードが実行される; `matt` が nil に設定される
    let anotherMatt = matt // `anotherMatt` は同じ型: `ImplicitlyUnwrappedOptional<person>`</person>

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

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


    // Swift 3.0

    let matt = Person(firstName: "Matt", lastName: "Mathias")
    matt?.firstName // `matt` is `Optional<person>`</person>
    let anotherMatt = matt // `anotherMatt` is also `Optional<person>`</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 はこのような不安全な状況を避けようとしています。Swift 開発者が ImplicitlyUnwrappedOptional に対して行った改善に感謝します。私たちは今、堅牢なコードを非常に簡単に書くことができます。もしかしたら、将来的には ImplicitlyUnwrappedOptional が私たちの視界から完全に消えるかもしれません。

最後に#

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

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