Posted at

理解があいまいな !型と?型の変数を違いを比較する

More than 3 years have passed since last update.

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の可能性がないので安全に扱える

?はどっちか分からん !は気をつけろ 無印は安全 と覚えると楽かもしれません。