読者です 読者をやめる 読者になる 読者になる

【Swift】Appleの新言語「Swift」のリファレンスを読む(13) - Optional Chaining

注意

  • あくまでメモ書きなので細かい部分を端折りますし、色々間違ってるかもしれません。ちゃんとした内容は原文を読んでね。
  • コード例は基本的に原文からそのまま引用していますが、ちょっとした注釈をつけたり、統合したりしています。
  • SyntaxHighlighterが対応してないので微妙に読みにくいです。SyntaxHighlighterはこちらのものを使用させて頂いてます。
  • 他言語にそっくりな部分でも指摘しない。(自戒)

Optional Chaining(オプションの連鎖)

Javaでこんなコードがあったとします。

public class Hoge {
    
    private Fuga fuga;
    public void setFuga(Fuga fuga) {
        this.fuga = fuga;
    }
    public Fuga getFuga() {
        return this.fuga;
    }
}

public class Fuga {
    
    public String piyo() {
        return "piyo";
    }
    
}

こんな呼び出し方をすると、NullPointerExceptionが出ます。Hoge#fugaはまだnullですからね。

Hoge hoge = new Hoge();
String piyo = hoge.getFuga().piyo();

あるクラスのあるプロパティがnullである可能性があるなら、慣例的にこんな感じのチェックをはさみます。

Hoge hoge = new Hoge();
String piyo;

Fuga fuga = hoge.getFuga();

if(fuga != null) {
    piyo = fuga.piyo();
}

こんなダサいことしなくてもいいようにしたいよね、と言うのがOptional Chainingです。

Optional Chaining as an Alternative to Forced Unwrapping

じゃあSwiftでも同じようなクラスを作ってみましょう。

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

Swiftでもこんな呼び出し方をすると実行時エラーになります。

let john = Person()

let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error

基本構文の章でも書きましたが、Swiftのifにはこの手のパターンのために便利な構文があります。

if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."

これはエラーになりません。「john.rersidence?」の時点でnilならすぐにelseに行ってくれます。便利ですね。

もちろんPerson#residenceに値を入れておけばtrue partが評価されますし、roomCountにはResidence#numberOfRoomsの値がちゃんと入ります。

john.residence = Residence()

if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}
// prints "John's residence has 1 room(s)."

Residence#numberOfRoomsの型はIntですが、Residence?経由で呼ばれるとInt?のように振舞う、ってことで「Optional Chain」と言う言葉を使っているのでしょう。

Defining Model Classes for Optional Chaining

Residenceクラスにさらに色々追加してみましょう。

class Residence {
    var rooms = Room[]()
    
    var numberOfRooms: Int {
        return rooms.count
    }
    
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    
    func printNumberOfRooms() {
        println("The number of rooms is \(numberOfRooms)")
    }
    
    var address: Address?
}

class Room {
    let name: String
    init(name: String) { self.name = name }
}

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?

    func buildingIdentifier() -> String? {
        if buildingName {
            return buildingName
        } else if buildingNumber {
            return buildingNumber
        } else {
            return nil
        }
    }
}

Calling Properties Through Optional Chaining

何の意味もないセクションなので割愛。

Calling Methods Through Optional Chaining

さて、まずはResidence#printNumberOfRooms()に注目しましょう。Residence#roomsの数を表示するメソッドです。

func printNumberOfRooms() {
    println("The number of rooms is \(numberOfRooms)")
}

これの返り値はVoidです。VoidだってVoid?になれます。だからこんなことが可能です。

let john = Person()

if john.residence?.printNumberOfRooms() {
    println("It was possible to print the number of rooms.")
} else {
    println("It was not possible to print the number of rooms.")
}
// prints "It was not possible to print the number of rooms."

Calling Subscripts Through Optional Chaining

Subsciptも返り値の型を持っているので同じようなことが出来ます。Person→Residence?→Room?→String?とちゃんと連鎖していきます。

if let firstRoomName = john.residence?[0].name {
    println("The first room name is \(firstRoomName).")
} else {
    println("Unable to retrieve the first room name.")
}
// prints "Unable to retrieve the first room name."

Residence#rooms[]に値を与えるとこんな感じ。

let johnsHouse = Residence()

johnsHouse.rooms += Room(name: "Living Room")
johnsHouse.rooms += Room(name: "Kitchen")

john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    println("The first room name is \(firstRoomName).")
} else {
    println("Unable to retrieve the first room name.")
}
// prints "The first room name is Living Room."

Linking Multiple Levels of Chaining

今johnと言うインスタンスにはResidenceが設定されています。が、Residence#addressはnilのままです。この状態でjohn.residence?.addressのプロパティを取得しようとしても、addressがnilなので当然nilになります。

if let johnsStreet = john.residence?.address?.street {
    println("John's street name is \(johnsStreet).")
} else {
    println("Unable to retrieve the address.")
}
// prints "Unable to retrieve the address."

値をセットすれば勿論取得できます。

let johnsAddress = Address()

johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence!.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
    println("John's street name is \(johnsStreet).")
} else {
    println("Unable to retrieve the address.")
}
// prints "John's street name is Laurel Street."

Chaining on Methods With Optional Return Values

Optionの連鎖はメソッドの返り値の型がOptionでも当然起こります。

if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {
    println("John's uppercase building identifier is \(upper).")
}
// prints "John's uppercase building identifier is THE LARCHES."

なんだか駆け足で説明してしまいましたが、Optionはnilが設定できることに意義があるのではなく、元々こう言う使い方をするものだと思います。