- 原文著者 :
Matt Mathias - 訳文出典 : 掘金翻訳計画
- 訳者 : Zheaoli
- 校正者: llp0574, thanksdanny
Swift がリリースされた後、Swift の開発者たちは、安全性とオプショナル型が Swift の最も重要な特徴の一つであると強調してきました。彼らは nil
の表現メカニズムを提供し、nil
になる可能性のあるインスタンスに対して明確な構文を使用することを要求しています。
オプショナル型には主に以下の 2 種類があります:
Optional
ImplicitlyUnwrappedOptional
最初のアプローチは安全な方法です:オプショナル型の変数を解体して基礎値にアクセスすることを要求します。2 つ目のアプローチは安全でない方法です:オプショナル型の変数を解体せずにその基礎値に直接アクセスできます。例えば、変数の値が nil
のときに ImplicitlyUnwrappedOptional
を使用すると、いくつかの例外が発生する可能性があります。
以下にこの問題に関する例を示します:
let x: Int! = nil
print(x) // クラッシュ! `x` は nil です!
Swift 3.0 では、Apple は ImplicitlyUnwrappedOptional
の実装を改善し、以前よりも安全になりました。ここで私たちは、Apple が Swift 3.0 で ImplicitlyUnwrappedOptional
に対してどのような改善を行ったのか、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 name
と last 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
上記の例では、nilPerson
は Optional<Person>
型のインスタンスです。これは、内部の値に正常にアクセスするためには nilPerson
をアンラップする必要があることを意味します。この場合、手動でのアンラップは非常に良い選択です。
安全な型宣言#
この変化は混乱を招くかもしれません。なぜ init!
を使用した初期化が Optional
型のインスタンスを生成するのでしょうか? init!
の !
は ImplicitlyUnwrappedOptional
を生成することを示しているのではないでしょうか?
答えは、安全性と宣言の依存関係にあります。上記のコード( let nilPerson = Person(firstName: "", lastName: "Mathias")
)では、コンパイラが nilPerson
の型を推論します。
Swift 2.x では、コンパイラは nilPerson
を ImplicitlyUnwrappedOptional<Person>
として処理します。理論的には、私たちはこのコンパイル方式に慣れており、ある程度の理にかなっています。要するに、Swift 2.x では、ImplicitlyUnwrappedOptional
を使用するには init!
を利用してインスタンスを初期化する必要がありました。
しかし、ある意味で、このアプローチは非常に安全ではありません。正直なところ、私たちは nilPerson
が ImplicitlyUnwrappedOptional
インスタンスであるべきだという明確な意図を持っていません。将来的にコンパイラが不安全な型情報を推論し、プログラムの実行に偏差を引き起こす場合、あなたたちも責任を負うことになります。
Swift 3.0 はこのような安全性の問題を解決する方法として、明示的に ImplicitlyUnwrappedOptional
を宣言しない限り、ImplicitlyUnwrappedOptional
を optional
として処理します。
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>` です
anotherMatt
は matt
と同じ型のインスタンスです。このような状況はあまり理想的ではないことを予想しているかもしれません。コード内で 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
が推奨する方法です。では、anotherMatt
を Optional<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 では、Swift は Optional
と ImplicitlyUnwrappedOptional
を同時に使用することを許可しています。ただし、明示的に宣言する必要があり、コンパイラに対して ImplicitlyUnwrappedOptional
を使用していることを伝える必要があります。
幸運なことに、コンパイラはもはや safeAgain
を ImplicitlyUnwrappedOptional
インスタンスとして自動的に処理しません。代わりに、コンパイラは safeAgain
変数を Optional
インスタンスとして処理します。このプロセスにおいて、Swift 3.0 は不安全なインスタンスの伝播を効果的に制限しました。
いくつかの言いたいこと#
ImplicitlyUnwrappedOptional
の変更は、私たちが通常 macOS または iOS 上で Objective-C で書かれた API を操作する際に起こる理由かもしれません。これらの API では、特定の状況下で返される値が nil
である可能性があり、Swift にとってはこの状況は安全ではありません。
したがって、Swift はこのような不安全な状況が発生するのを避けています。ImplicitlyUnwrappedOptional
に対する Swift 開発者の改善に感謝します。私たちは今、堅牢なコードを書くことが非常に簡単になりました。もしかしたら、将来的には ImplicitlyUnwrappedOptional
が完全に私たちの視界から消えるかもしれません。
最後に#
この分野についてもっと知りたい場合は、こちらの this proposal から有用な情報を得ることができます。issue では、この提案の著者の考えを得ることができ、具体的な変更を通じて詳細を理解することができます。また、そこには関連するコミュニティの議論へのリンクもあります。