オプショナルの連鎖

オプショナル型のプロパティにアクセスする場合、プロパティの後ろに ! をつけてアンラップが必要になります。


class Person {
    let name: String
    let friends: [Person]?
    init(name: String) {
        self.name = name
    }
}

let taro = Person(name: "タロウ")
print(taro.friends!.count)

ところが、オプショナル型のプロパティがnilの場合、アンラップすると実行時エラーになります。これを回避するには、オプショナルバインディングで事前に確認する方法があります。


if let friends = taro.friends {
    print(friends.count)
}

オプショナルの連鎖を使うと、これをもう少し簡単に記述することができます。
オプショナルの連鎖では、プロパティの後ろに、 ! の代わりに ? をつけてアクセスします。そして、もしそのプロバティがnilだった場合、エラーになるのではなく、nilが返されます。


print(taro.friends?.count)    // nil

オプショナルの連鎖から返される型は、期待する型のオプショナル型です。これはnilが返される可能性があるからです。上の例の場合、配列のcountプロパティはInt型なので、Intのオプショナル型(Int?)が返されます。

次のように、取得した値をオプショナルバインディングを使ってアンラップできます。


if let count = taro.friends?.count {
    print(count)
}

次はもう少し複雑な例をみてみます。

映画館、映画館の中の各スクリーン、各スクリーンで上映する映画と座席数という、以下の図のような関連を考えます。

opt_chain.png

これらのクラスをコードで記述してみます。


/* 映画館クラス */
class MovieTheater {
    let name: String            // 名称
    var screens: [Screen]?      // スクリーン
    // イニシャライザ
    init(_ name: String) {
        self.name = name
    }
}

/* スクリーンクラス */
class Screen {
    var movie: Movie?           // 映画
    var sheets: [Bool]          // 座席
    // 各座席にアクセスするためのサブスクリプト
    subscript(idx: Int) -> Bool {
        get {
            return sheets[idx]
        }
        set {
            sheets[idx] = newValue
        }
    }
    // 座席数
    var numberOfSheets: Int {
        return sheets.count
    }
    // イニシャライザ
    init(numberOfSheets: Int) {
        self.sheets = Array(count: numberOfSheets, repeatedValue: false)
    }
}

/* 映画クラス */
class Movie {
    let title: String           // タイトル
    var director: String?       // 監督
    var leadingActors: String?  // 主演
    // イニシャライザ
    init(_ title: String) {
        self.title = title
    }
    // 説明
    func printDescription() {
        print("タイトル:\(title)")
        if let director = self.director {
            print(" 監督:\(director)")
        }
        if let leadingActors = self.leadingActors {
            print(" 主演:\(leadingActors)")
        }
        print()
    }
}

映画館クラスは、スクリーンの配列をオプショナル型のプロパティとして持っています。スクリーンクラスの座席は空き状況をBool型(空席ならfalse)で保持する配列で、イニシャライザで受け取った座席数の大きさの配列に初期化しています。また座席の空き状況に直接アクセスできるようにサブスクリプトを使っています。映画クラスは、タイトル、監督名、主演俳優名をプロパティに持つ単純なクラスです。

まず、映画館クラスのインスタンスを生成してみます。


// 映画館
let theater = MovieTheater("りんごシアター")

映画館クラスのスクリーンの配列はオプショナル型ですが、オプショナルの連鎖を使って安全にアクセスできます。次は映画館クラスのスクリーン数を取得しています。


if let numScreens = theater.screens?.count {
    print("\(theater.name)のスクリーン数は\(numScreens)です。")
} else {
    print("\(theater.name)にスクリーンは有りません。")
}
/* 実行結果
  りんごシアターにスクリーンは有りません。
*/

今度は映画館にスクリーンと映画を設定してみます。


// スクリーンA
let screenA = Screen(numberOfSheets: 150)
let dieHard = Movie("ダイ・ハード")
dieHard.director = "ジョン・マクティアナン"
dieHard.leadingActors = "ブルース・ウィリス"
screenA.movie = dieHard

// スクリーンB
let screenB = Screen(numberOfSheets: 200)
let terminator = Movie("ターミネーター")
terminator.director = "ジェームズ・キャメロン"
terminator.leadingActors = "アーノルド・シュワルツェネッガー"
screenB.movie = terminator

theater.screens = [screenA, screenB]

if let numScreens = theater.screens?.count {
    print("\(theater.name)のスクリーン数は\(numScreens)です。")
} else {
    print("\(theater.name)にスクリーンは有りません。")
}
/* 実行結果
  りんごシアターのスクリーン数は2です。
*/

最初のスクリーンの上映映画のタイトルを表示してみます。


if let title = theater.screens?[0].movie?.title {
    print(title)
}
/* 実行結果
   ダイハード
*/

スクリーンクラスの映画プロパティもオプショナル型なので、movieの後に?をつけてアクセスしています。このように、オプショナル型へのアクセスを繋いで一回でアクセスできます。
この連鎖の途中のオプショナル型がnilだった場合は、nilが返されます。また、映画クラスのタイトルはString型なので、この連鎖から返される値は、Stringのオプショナル型となります。上では、オプショナルバインディングを使って戻り値をアンラップしています。

これをオプショナルの連鎖を使わずに記述すると次のようになります。


if let screens = theater.screens {
    if let movie = screens[0].movie {
        print(movie.title)
    }
}
/* 実行結果
   ダイハード
*/

このように、オプショナルの連鎖を使わない場合、階層の途中のオプショナル型の数が増えるほどアンラップの確認のための記述が増えます。

本来はスクリーン配列の範囲チェックも必要ですがここでは省いています。下の座席配列も同様です。

次は、2番目のスクリーンの、25番目のシートの空席状況を調べます。


if let sheet = theater.screens?[1][24] {
    print(sheet ? "埋まってます。" : "空席です。")
}
/* 実行結果
   空席です。
*/

空席状況はスクリーンクラスのサブスクリプトで取得できるので、2番目のスクリーン(theater.screens?[1])で返されるスクリーンのインスタンスから25番目の空席状況を取得するために、[24]でアクセスしています。

仮に、スクリーンクラスの座席配列の型がBool型でなく、何かの型のオプショナル型だったとしたら、次の様にサプスクリプトから取り出す[]の後に、?をつけて同じ様にアクセスできます。

theater.screens?[1][24]?.hogehoge

値を設定もオプショナルの連鎖を使って安全にできます。


theater.screens?[1][24] = true
if let sheet = theater.screens?[1][24] {
    print(sheet ? "埋まってます。" : "空席です。")
}
/* 実行結果 
   埋まってます。
*/

もし、連鎖の途中のオプショナル型の何れかがnilであった場合、値は設定されずエラーも発生しません。

次は、最初のスクリーンの映画監督を出力します。


if let director = theater.screens?[0].movie?.director {
    print(director)
}
/* 実行結果
   ジョン・マクティアナン
*/

映画クラスの監督プロパティは、Stringのオプショナル型ですが、その場合でもオプショナルの連鎖から返される方は、Stringのオプショナル型です。String型の場合はString型のオプショナル型が返されますが、String型のオプショナル型の場合でも同様にString型のオプショナル型が返されるということです。取得する型がオプショナル型かどうかは、オプショナルの連鎖から返される型には影響しません。

値の変更も同じ様にできます。


theater.screens?[0].movie?.director = "ジョン・マクティアナン(John McTiernan)"

もし連鎖のいずれかのオプショナル型がnilのため値を変更できない場合は、nilが返されるため、次のようにして値を変更できたか確認することができます。


if (theater.screens?[0].movie?.director = "ジョン・マクティアナン(John McTiernan)") != nil {
    print("監督名を変更しました。")
}

インスタンスメソッドの呼び出しも同様です。


theater.screens?[1].movie?.printDescription()
/* 実行結果
   タイトル:ターミネーター 監督:ジェームズ・キャメロン 主演:アーノルド・シュワルツェネッガー
*/

オプショナルの連鎖の途中がnilだった場合、メソッドは実行されません。

値を返さないメソッドの戻り値の型はVoidであるため、オプショナルの連鎖によるメソッド呼び出しの型はVoid?となります。次のように、戻り値をnilと比較して、メソッドが実行されたか判断することができます。


if theater.screens?[1].movie?.printDescription() != nil {
    print("メソッドは実行されました。")
}

もし事前にメソッドが実行可能かどうかを確認したい場合は、次のようにメソッド自体を戻り値としてアンラップ後実行することができます。


if let method = theater.screens?[1].movie?.printDescription {
    method()
}
/* 実行結果
   タイトル:ターミネーター 監督:ジェームズ・キャメロン 主演:アーノルド・シュワルツェネッガー
*/

ここで、theater.screens?[1].movie?.printDescription から返される型は、printDescriptionメソッドの型 () -> () のオプショナル型ということになります。つまり、次の様に記述するのと同義です。


let method: (() -> ())? = theater.screens?[1].movie?.printDescription
if (method != nil) {
    method!()
}