- 原文著者 : Matt Mathias
- 訳文出典 : 掘金翻訳計画
- 訳者 : Zheaoli
- 校正者: llp0574, thanksdanny
Swift がリリースされた後、Swift の開発者たちは、安全性とオプショナル型が Swift の最も重要な特徴の一つであると強調してきました。彼らは nil
を表すメカニズムを提供し、nil
になる可能性のあるインスタンスに対して明確な構文を使用することを要求しています。
オプショナル型には主に以下の 2 種類があります:
Optional
ImplicitlyUnwrappedOptional
最初の方法は安全な方法です:オプショナル型の変数を解体して基本値にアクセスすることを要求します。2 番目の方法は安全でない方法です:オプショナル型の変数を解体せずにその基礎値に直接アクセスできます。たとえば、変数の値が nil
のときに ImplicitlyUnwrappedOptional
を使用すると、いくつかの例外が発生する可能性があります。
以下にこの問題に関する例を示します:
let x: Int! = nil
print(x) // Crash! `x` is 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 // 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
上記の例では、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` is `ImplicitlyUnwrappedOptional<person>`; we can access `firstName` directly</person>
let anotherMatt = matt // `anotherMatt` is also `ImplicitlyUnwrappedOptional<person>`</person>
anotherMatt
は matt
と同じ型のインスタンスです。このような状況はあまり理想的ではないことが予想されます。コード内で 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
が推奨する方法です。それでは、anotherMatt
を Optional<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 では、Swift は Optional
と ImplicitlyUnwrappedOptional
を同時に使用することを許可しています。しかし、私たちは明示的に宣言する必要があります。これにより、コンパイラに私たちが使用しているのは ImplicitlyUnwrappedOptional
であることを伝えます。
幸運なことに、コンパイラはもはや safeAgain
を ImplicitlyUnwrappedOptional
インスタンスとして自動的に処理しません。代わりに、コンパイラは safeAgain
変数を Optional
インスタンスとして処理します。このプロセスにおいて、Swift 3.0 は不安全なインスタンスの伝播を効果的に制限しました。
いくつかの言いたいこと#
ImplicitlyUnwrappedOptional
の変更は、私たちが通常 macOS または iOS 上で Objective-C で書かれた API を操作する際に起こる理由かもしれません。これらの API では、特定の状況下で返される値が nil
である可能性があり、Swift にとってはこの状況は安全ではありません。
したがって、Swift はこのような不安全な状況を避けようとしています。Swift 開発者が ImplicitlyUnwrappedOptional
に対して行った改善に感謝します。私たちは今、堅牢なコードを非常に簡単に書くことができます。もしかしたら、将来的には ImplicitlyUnwrappedOptional
が私たちの視界から完全に消えるかもしれません。
最後に#
この分野についてもっと知りたい場合は、こちらの この提案 から有用な情報を得ることができます。issue からこの提案の著者の考えを得ることができ、具体的な変化を通じて詳細を理解することができます。また、そこには関連するコミュニティの議論のリンクもあります。