Swiftのオプショナルのオプショナルて何??


オプショナルのオプショナルはあります。
正式な呼び方がなんなのかはわからないけど確かにあります。ちょっと長くなるけど調べたときのいきさつ形式で書きます。

エラーハンドリングについて調べていたときに以下のコンパイルエラーに遭遇しました。

Value of optional type 'Int?' not unwrapped; did you mean to use 'try!' or chain with '?'?
オプショナルの値のInt?はアンラップされません、やろうとしていることはtry!か、?を使った連鎖で出来ませんか?

このエラーは以下のようなプログラムの最後にある try? で出ました。実行はPlaygroundです。
enum MyError: ErrorType {
  case myError
}

func someThrowingFunction(a: Int) throws -> Int {
  if 1 < a {
    throw MyError.myError
  }
  return a
}

func someFunction() {
  let a: Int = try? someThrowingFunction(5) //ここでエラー発生
}
このエラーメッセージは、「try? というのは戻り値を格納する値の型をオプショナル型にする必要があるのでして下さい」という趣旨のメッセージです。ここでは最後のaの宣言を Int? にすればよいです(または型を書かずにコンパイラに推論させればよい)。
まあこれでこの件は解決なんですが、ただ、メッセージの最後の chain with '?' (?を使った連鎖)が気になりました。chainは連ねるという意味ですから??のように重ねること?なぜこの状況でchainが出てくるのか?といろいろ考えているうちに、もしかして ?? というものがあるのでは?と思い、以下のプログラムを試してみました。メソッドの戻り値を Int? にしてみました。
//戻りをInt?に変更
func someThrowingFunctionOPT(a: Int) throws -> Int? {
  if 1 < a {
    throw MyError.myError
  } else if 0 <= a {
    return a
  } else {
    return nil
  }
}

func someFunction() {
  //aの型をInt?に変更
  let a: Int? = try? someThrowingFunctionOPT(5) //やはりエラー
}
こんどは最後の文でエラーメッセージが
Value of optional type 'Int??' not unwrapped...
とさっきの Int? の部分が Int?? になりました。
そこでその部分を
func someFunction() {
  //aの型をInt??に変更
  let a: Int?? = try? someThrowingFunctionOPT(5) //エラー消える
}
とするとメッセージは消えます。
a.dynamicType を調べると
Optional<Optional<Int>>
となりました。どうやらaはIntのオプショナルのオプショナルという型になったようです。恥ずかしながらオプショナルのオプショナルというものは初めて知りました。

で、さらに疑問が出てきます。もっと連結できるんではないかと。
func someThrowingFunctionOPT(a: Int) throws -> Int? {
 if 1 < a {
    throw MyError.myError
  } else if 0 <= a {
    return a
  } else {
    return nil
  }
}

//戻りがInt??のメソッドを追加
func someThrowingFunctionOPT2(a: Int) throws -> Int?? {
  let a: Int?? = try? someThrowingFunctionOPT(5)
  return a
}

func someFunction() {
  //aの型をInt???にするとエラーが消える
  let a: Int??? = try? someThrowingFunctionOPT2(5)
  a.dynamicType
}
これも同じように最後のaの型を Int?? では例のエラーメッセージが出て、Int??? にすると消えるパターンでした。a.dynamicType も
Optional<Optional<Optional<Int>>>
となりました。
きりがないのでもうやりません。

で、初めの疑問に戻りますが、chain with '?' は宣言した値の型に?をいくつかつけることで解決するのではないかという意味だと推測します。chainといっても1も含みます。なのでInt から Int? へのおすすめをする状況でもchianどうですか?と出てきます。メッセージが長くなるのをいやがって1も含むことにしたのかもしれません。ちなみに?の連鎖ならchain of ?なのではないか、withを使っているといるということは何か別の解釈があるのではないかという気もしますが。

で、やっとタイトルに対する結論
オプショナル型は入れ子にすることができる。オプショナルの中にオプショナルを入れることが出来る。

Int?型で中身nilのものをさらにオプショナルで2回くるんで(Int???)実行すると
//a: Int???
if let aa = a {
  //走る
}
if let aa = a! {
  //走る
}
if let aa = a!! {
  //走らない
}
となる。おもしろい。

次に、nilか非nilかのチェックをisで行おうとしてみます。このisの使い方は正式ではないのでエラーが出ます。ここではわざとエラーを出します。このエラーではアンラップの提案をされますが
//a: Int???
if a is Int { //Downcast from 'Int???' to 'Int' only unwraps optionals; did you mean to use '!!!'?
  
}
出てくるエラーは
Downcast from 'Int???' to 'Int' only unwraps optionals; did you mean to use '!!!'?
!!!してはどうですか?と提案されます。
この提案がプログラマの助けになる提案なのかどうなのかは置いといて、!!!が正式なものとされている様です。

???とか!!!とか初めて見たときは冗談かと思ったのですが、エラーが出たときに実行を続けようとする仕様を実装しようとするとこうなってしまうということなんでしょう。多重ラップの必要性はエラー処理以外でもあるのでしょうか。詳しい方ならわかるかもしれません。

コメント

このブログの人気の投稿

Swiftのコンパイルエラー寄せ集め

コンパイルエラー覚え書き(Objective-C)

AVAudioSession細かいことまとめ(late 2014)