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

  • 41
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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

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

  • この記事は以下の記事からリンクされています
  • Optional型の勘所からリンク