swiftでの変数に ! や ?、および無印がありますが、いまいち理解があいまいだったのでPlaygroundで比較してみました。
Optional型がどうこうとか、そういう理論的な話は抜きです。
どういう場合にビルドエラーになるのか、どういう時に実行時エラーになるのかが中心です。
特に、普段使わない !型で宣言した変数の扱い について見ていきます。
長いので先に結論です
- ?型変数 はnilの可能性がある。末尾に ? か ! を付けて適切に処理するように。
- !型変数 はnilの可能性がある。nil だと落ちるので注意するように。
- 無印はnilの可能性がないので安全に扱える
代入
まずは変数宣言と、単純な値の代入の実験です。
var hoge1 : String? = nil //OK
var hoge2 : String! = nil //OK
var hoge3 : String = nil //ビルドエラー
意外だったのは、 !がnil許容 だったことです。
ずっと !はnil非許容と勘違いしてました。
実行
次にnil許容な変数に対する何らかの操作を行った場合です。
//?
var hoge1 : String? = ...
hoge1.lowerCaseString //ビルドエラー
hoge1?.lowerCaseString //String?型の値が返る
hoge1!.lowerCaseString //String型が返る。nilだと実行時クラッシュ
//!
var hoge2 : String! = ...
hoge2.lowerCaseString //String型が返る。nilだと実行時クラッシュ
//無印はそもそもコンパイラの段階でnilを許容しないので省略
値の受け渡し
ちょっと複雑になります。次にそれぞれの型の値の受け渡しです。
?マークへの代入
var h1 : String? = ...
var h2 : String! = ...
var h3 : String = ...
var value : String?
value = h1 //OK。これはそのまま
value = h1? //OK。?を付けても付けなくても一緒
value = h1! //nilだと実行時エラー
value = h2 //OK。nilでも左辺がString?型だから問題なし
value = h3 //OK。確実に値が入る
!マーク
var h1 : String? = ...
var h2 : String! = ...
var h3 : String = ...
var value : String!
value = h1 //OK h1がnilだとvalueもnil
value = h1? //OK h1がnilだとvalueもnil
value = h1! //h1がnilだと実行時エラー
value = h2 //OK。h2がnilだとh1もnil
value = h3 //OK。確実に値が入る
無印
var h1 : String? = ...
var h2 : String! = ...
var h3 : String = ...
var value : String
value = h1 //ビルドエラー
value = h1? //ビルドエラー
value = h1! //nilだと実行時エラー
value = h2 //nilだと実行時エラー
value = h3 //OK
一覧表
表にしてまとめてみます
危険 は右辺がnilの場合に実行時例外を意味します
つまり、危険のパターンはif等で事前に判定しておく必要があります。
左辺\右辺 | ?(生) | ?(?つき) | ?(!つき) | !型 | 無印 |
---|---|---|---|---|---|
?型= | OK | OK | 危険 | OK | OK |
!型= | OK | OK | 危険 | OK | OK |
無印= | ビルド不可 | ビルド不可 | 危険 | 危険 | OK |
関数呼び出し
関数呼び出しについても代入と同様です。
受け取り手のメソッドの引数が、?や無印のときは問題ないと思います。
?のときは ? や ! を付ける、無印のときはnilの可能性はないのでそのまま実行してOKです。
func test1(str : String?) {
let lower = str?.lowerCaseString //?なり!なりつけるだけ
}
func test3(str : String) {
let lowser = str.lowerCaseString //nilの可能性がない
//逆に if str != nil と書くとビルドエラー。nilの可能性がないと言われる
}
一方、!はnilの可能性があるので ifでnilチェック する必要があります。
func test3(str : String!) {
let lowser = str.lowerCaseString //もしstrがnilなら落ちる
//よってこうする必要がある
if str != nil {
let lower = str.lowerCaseString
}
}
正直なところ、自分で作るメソッドなら引数は ! ではなく ? のほうがいいと思います。
nilの可能性がないなら無印にすべきです。(普通作らないとは思いますけど...)
ではなぜ ! があるかというと、 Objective-C時代の遺産 が多いです。
delegate系に多く存在します。
当然ながらObj-CにOptional型なんてないので、 nilの可能性がある引数は 基本!付きになります
(※このためだけに!型が用意されたとしか思えない...)
Delegate等での!型引数の場合、基本的にnilチェックをしてください。
もしくは一旦 ?型の変数に入れてOptionalChaining にするのも手です。
まとめ
再びまとめです。
・ ?型変数 はnilの可能性がある。末尾に?か!を付けて適切に処理するように。
・ !型変数 はnilの可能性がある。nilだと落ちるので注意するように。
・ 無印 はnilの可能性がないので安全に扱える
?はどっちか分からん !は気をつけろ 無印は安全 と覚えると楽かもしれません。