[iOS] Swiftの変数の型の後ろについている!とか?っていうのは何?

2014/07/8

こんにちは。きんくまです。

この間発表されたSwiftなんですけど、ちょっと触ってみてます。

それで、!とか?の役割がイマイチわかんなかったので、調べてみました。

今回の参考リンクです。

>> [Swift] Optional型についてのまとめ (Quiita)
>> What does an exclamation mark mean in the Swift language? (Stack Overflow)

上のリンクで最初の記事がわかりやすかったので、こちらを使って自分でもコードを書いてみます。
まず前準備で、クラスを作ります。

class Dog
{
    func bark()->String
    {
        return "Wan!"
    }
}

!も?もつかない、これまでの場合

// ? ! なしの状態

var pochi:Dog = Dog()

pochi.bark() //-> "Wan!" #1

pochi?.bark() //コンパイルエラー optionalじゃないから #2
pochi!.bark() //コンパイルエラー optionalじゃないから #3

pochi = nil //コンパイルエラー #4

変数の宣言時に!も?もつかない場合は、普通にメソッドが呼び出せました。#1

#2と#3の場合、?と!を付けてメソッドを呼び出そうとすると、コンパイルエラーが出ました。
optionalじゃないのに、optionalの時のやり方をしようとしたからです。

nilを代入しようとしたらエラーです #4 。つまり、オプショナルな記号をつけない場合はnilは許容されません。値がある必要があります。

ちょっと脱線 インスタンスのポインタのvarとlet


var pochi:Dog = Dog()

var pochi2:Dog = Dog()
pochi2 = pochi

let pochi3:Dog = Dog()
pochi3 = pochi2 //コンパイルエラー

swiftでは変数の宣言にvarとletのキーワードがあります。varは宣言後に代入できますが、letは宣言時のみです。
文字列や数値では、letの変数に代入するとコンパイルエラーが出ます。

インスタンスのポインタにletを使った場合は、別のポインタを代入するとコンパイルエラーになりました。
上の例でも、varの場合は別のポインタをつっこめましたが、letの場合は駄目でした。

なので、インスタンス化する場合のポインタは、使い回さない限り、他のポインタを代入できないletを使っておいた方がいいみたい。

? つきの変数

話を戻して ? つきの変数の挙動です。

// ?つき

var john:Dog? = Dog()

john.bark() //コンパイルエラー #1

//unwrap
john!.bark() //-> "Wan!" #2
john?.bark() //-> {Some "Wan!"} #3

//#4
if let j = john {
    println(j.bark()) //-> "Wan"
}


john = nil //OK #5


//nilの状態でやってみる

john!.bark() //ランタイムエラー #6
john?.bark() //nil #7

//#8
if let j = john {
    println(j.bark()) //ここは通らない
}

?がついている場合は、さきほどと違ってnilになっても大丈夫です。#5
またこのときwrapの状態の変数になっています。wrapの状態とは「中身が何かわかんないけど、箱として存在している」というものみたいです。

なので、中身が何かわかんない状態なので、Dog型にbarkっていうメソッドが存在しているかコンパイラはわからないので、エラーになります。#1(Dog型に?つきで宣言しているのに、、)
なので、そういう場合に、箱を空けてあげます(unwrap)。

unwrapの方法は上のリンクのstackoverflowの記事によると3つあります。

1. forced unwrapping
! をつけてその型にキャストする方法
上のコードだと #2、#6 です。

キャストするので、もし中身がnilだった場合 #6 は、ランタイムエラーになります。

2. optional chaining
? をつけてその型だと思ってbarkを呼び出す方法
上のコードだと #3、#7 です

optional chainingはその値がnilだったら、その場でnilを返すやり方みたい。
さっきの #6 はランタイムエラーになったのに、#7の場合はnilが返ってそのままプログラムは続きます。
また、Playground環境(プロジェクトの一種)だと吐き出される値も、#2の場合はそのまま返ってきているのに、#3の場合はSomeとつけているので、キャストして、完全にその型に変換しているというよりは、そいういうメソッドがあると仮定して呼び出すみたいなイメージだと思われます。

ちなみにprintlnして、Consoleで見ると #2 も #3 もどちらとも「Wan!」って出力されます。(Someってつかない)

また、optional chaining っていうぐらいなので、

foo?.bar()?.hoge っていう感じに、nilにならない限りメソッドやプロパティを連結できるのも特徴です。

>> Optional Chaining(Appleのドキュメント)

3. optional binding
上のコードだと #4, #8 です

optional bindingは、Appleのドキュメントによると、if文とwhile文で使えるやり方で、
「条件式部分に、変数を宣言して代入、そのとき、その変数に値があったら、その中身を実行(中でその変数をそのまま使える)」というものみたい。

! つきの変数

// !つき

var hachi:Dog! = Dog()

hachi.bark() //OK -> "Wan!" #1

//unwrap
hachi!.bark() //-> "Wan!" #2
hachi?.bark() //-> {Some "Wan!"} #3


hachi = nil //OK #4

//nilの状態でやってみる

hachi.bark() //ランタイムエラー #5
hachi!.bark() //ランタイムエラー #6
hachi?.bark() //nil #7

!つきの場合は、?つきの場合と同じところと、いくつか違うところがありました。
まず、nilは代入できます #4

?つきでは、コンパイルエラーになっていた そのままメソッドを呼び出す方法はエラーになりません。#1, #5
ただ、#5 ではnilのメソッドを呼び出そうとするので、ランタイムエラーになります。

unwrapのときは、?つきと全く同じ結果になりました。#2, #3, #6, #7

まとめ

以上のことからまとめると、

変数がnilじゃなく値があるとき

変数の宣言 普通に呼び出し(foo.bar) forced unwrapping (foo!.bar) optional chaining(foo?.bar) nilの代入(foo = nil)
foo OK コンパイルエラー コンパイルエラー コンパイルエラー
foo? コンパイルエラー OK OK {Some} OK
foo! OK OK OK {Some} OK

 

変数がnilのとき

変数の宣言 forced unwrapping (foo!.bar) optional chaining(foo?.bar)
foo? ランタイムエラー nilが返る
foo! ランタイムエラー nilが返る

 

もともとはUITableViewDataSourceで、

func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int

となっていて、引数のtableView: UITableView! の最後の! は何だ?というところから調べてみました。
変数の後ろに! がついている場合は、表を見るとわかるように ついてない場合の上位互換みたいなもので、nilが中身に入っていてもOKだし、unwrapもできるというものでした。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る