SwiftのoptionalとObjective Cのnilの違い

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

この投稿は@ylisrさんの投稿を、本人の許可をとってざっと翻訳したものです。
僕自身はまだSwiftのコードを一行も書いていないので、万が一間違えた訳があったらごめんなさい。俺、Poinの1.9が無事リリースできたら、XCode6にするんだ。。。

元記事 Swift's optional is not Objective-c nil by @ylisr


SwiftのOptionalタイプの基本的な使い方の説明は、Qiitaにも何件か登録されています。たとえば、
[Swift] Swiftのoptional valueの便利さ /「?」と「!」でより堅牢なコードへ
Swiftの「?」とか「!」などのOptional Valueの挙動を調べてみた
SwiftのOptionalとType Safety

この投稿ではもう少し掘り下げて、Optionalが本質的には何で、カーテンの向こうではどのように動作しているかを解説したいと思います。SwiftのOptional型と、Objective Cのnil型を混同してつまずく人が多いので、まずはその説明から始めます。それから、optional chainingoptional binding の使い方について解説します。

定義: Optionalとは、Swiftにおけるnil、もしくはnilではない値をとる変数、もしくは定数です。

optionals.swift
var strValue: String?  //下記と同じ意味:
var strValue: Optional<String> 

?はOptinalの 値型 を定義するためのシンタックスシュガーです。String? と書いた場合の意味は、 Optionalになり得る String型を定義しているのではなく Stringもしくはnilを格納できる Optional型を定義しています。

Swiftにおけるnilは、Objective Cにおけるnilとは異なります。Objective Cにおいては、nilは存在しないオブジェクトへのポインタを意味します。対して、Swiftではnilはポインターではありません。ある型の値の不在を意味します。そのため、オブジェクトだけでなく、任意のOptional型に対してnilを設定する事が出来ます。The Swift Programming Language

まず、Swiftにおけるすべての値型は、使用する前にデフォルト値で初期化する必要があります。そのため、nilかも知れない文字列のようなものはありません。次に、Optional値は直接使用する事ができません。使用するためにはかならずunwrapする必要があります。(ここで後述する!、optional chaining、optional bidingが活躍することになります)

下記のようにOptionalを使っている例を見かけます。

optionals.swift
var optional:String?
var mustBeThere = optional! 

Optionalに値が設定されているかどうか確信が持てない場合には、あまりいい使い方とは思えません。値が設定されていないOptionalに対してunwrapを実行するとランタイムエラーが発生してしまうため、強制的にunwrapを行う!演算子を使用する場合には細心の注意が必要です。

まだすっきりしないのであれば、Optionalが実際に何なのかを見てみましょう。下記はXcode6で見られる実装です。

optionals.swift
enum Optional<T> : LogicValue, Reflectable {
    case None
    case Some(T)
    init()
    init(_ some: T)

    /// Allow use in a Boolean context.
    func getLogicValue() -> Bool

    /// Haskell's fmap, which was mis-named
    func map<U>(f: (T) -> U) -> U?
    func getMirror() -> Mirror
}

そうです、これはenumなんです。正確には、保持する値型をもつ .Some と、nilである .None という二つのオプションを持つenumです。

さて、もう一度これを読んでみましょう。

optionals.swift
let optionalTen: Int? = 10

このOptional定義は「これはInt型で、Optionalです。」という意味ではなく、「これはOptionalで、Intを保持しているかもしれないし、していないかもしれません」という意味です。
実際、OptionalはInt、String、Arrayなどと同じ値型です。ちょっとつかめてきましたか? Objective Cではnil値が利用できます。そのため、Objective CのコードをSwiftにブリッジして使用する場合には、すべてのObjective C型はOptinal型として定義されます。

Unwrap時のルール: Optional型で保持された変数の値を直接使用することは出来ません。

optionals.swift
class Person {
  var name: String
  init(_ name: String) {
    self.name = name
  }
}
var p: Person? = Person("John")  // pはOptionalでnilかもしれない
var greeting = "Hello" + p.name // コンパイル時エラー
var greeting = "Hello" + p!.name // “Hello John”

!演算子はOptinalをunwrapして、保持している値を取り出します。(この例ではJohnという文字列)もしpがnilの場合には、このコードはランタイムエラーを発生させます。

Objective Cにおいてどのようにnilチェックするかを覚えていますか?

file.m
if (!testVar) {
    NSLog(@"testVar is nil");
} else { 
    NSLog(@"testVar is equal to %@", testVar);
}

Swiftでは、if letという、optional binding を使用します。

optionals.swift
var testVar: String? = myRandomFunction() /*nilを返すかもしれない*/
if let constVar = testVar { 
 println("testVar is equal to \(constVar)")
} else { 
 println("testVar is equal to nil")
}

Optionalをunwrapする場合、!演算子よりも、このoptional bindingを使う方が安全であり、好ましい方法です。

Objective C APIを一つ例として書いてみましょう。

optionals.swift
let formatter = NSDateFormatter()
let now = formatter.dateFromString("not_valid")
let soon = now!.dateByAddingTimeInterval(5.0) /*ランタイムエラー*/
/*Objective Cにおけるコードでは、ランタイムエラーは発生せずsoonはnilとなる */

/*if letを使ってnilチェック*/
let formatter = NSDateFormatter()
if let now = formatter.dateFromString("not_valid"){
  let soon = now.dateByAddingTimeInterval(5.0) 
  // now,soonにはNSDateが設定されている
} else {
  // nowはnil
}

Optional Chaining — ?演算子を用いる事で、複数のOptionalを連結して、そのチェーンに含まれるすべてのOptionalが値をもつかどうかを確認できます。

optionals.swift
var testVar = varOne?.varTwo?.varThree
if let varOne = optionalFuncOne() { 
    if let varTwo = optionalFuncTwo() { 
        if let varThree = optionalFuncThree() { 
            testVar = varThree
        }
    }
}

これもシンタックスシュガーであり、水面下ではこれはネストされたOptional Bidingであると理解する事ができます。

さて、新しく得た知見を活かして、WWDCセッション404に出て来た関数をみてみましょう。

file.swift
func stateFromPlist(list: Dictionary<String, AnyObject>) -> State? {
  switch (list["name"], list["population"], list["abbr"]) {
      case (.Some(let listName as NSString),
      .Some(let pop as NSNumber),
      .Some(let abbr as NSString))
      where abbr.length == 2:
          return State(name: listName, population: pop, abbr: abbr)
      default:
          return nil
  }
}

わかりましたか? この .Some がどこからきたかわかりますか?

SwiftのOptionalは安全にコードを書くためのとてもクールな機能です。みなさんSwiftを楽しんでますか?