Menu

Category

Archive

logo


The Swift Programming Language ~ Optional Chaining

2014-06-25 10:00:00 +0900
  • このエントリーをはてなブックマークに追加

iBooks にある “The Swift Programming Language” の勉強メモ。Objective-C と C を普段書いている自分から、ちょっと馴染みがないものを特にまとめておきます。目次は こちら

今回は、Optional Chaining に関して。

Optional Chaining

Optional Chaining とは、nil である可能性のあるプロパティ・メソッド・サブスクリプトを呼び出す際の便利な方法です。いくつものクエリがある場合に、一つずつ nil であるかを確認することなく、うまく処理してくれます。

1. Optional Chaining と Forced Unwrapping (!)

Optional 値の後に ? をつけて、Optiona Chaining を指定します。これは、! と似ています。大きな違いは、nil に対して ! するとランタイムエラーを起こしますが、? の場合には、nil を返します。結果、たとえ呼び出したいメソッド等の返り値が Int であっても、Optional Chaining を使用した場合、Int? となります。

ここからどのように Optional Chaining を使用するのかをみていきます。

1
2
3
4
5
6
7
class Person {
    var residence: Residence?
}
 
class Residence {
    var numberOfRooms = 1
}

分かりやすいクラスだと思います。

これをもとに !? の違いをみていきます。

1
2
3
let john = Person()
let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error

この Person のインスタンスのリファレンスを持っている john のプロパティ residence は nil です。したがって、この nil に対して、! (forced unwrapping) するとランタイムエラーが生じます。

次に Optional Chaining の例です。

1
2
3
4
5
6
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.residence?.numberOfRooms が Optional Chaining です。Person インスタンスは、Residence インスタンスを持っているか分からないので、? を使用することにより、もし、nil の場合には、nil を返してくれます。

2. 複雑なモデルクラスの Optional Chaining

1 でみたように、Optional Chaining は、安全に値やメソッドにアクセスすることができます。この機能を使用して、何層にもなったクラス関係やサブクラスを使用しての複雑な処理も可能です。

まずは、この例のためにクラスを用意します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Person {
    var residence: 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
        }
    }
}

先ほどの例の Residence は今回、Room インスタンスの配列を保持し、サブクスクリプトも定義されています。また、Optional な Address インスタンスも保持しています。

まずは、メソッドを呼んでみます。

1
2
3
4
5
6
7
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."

john.residence は nil なので、nil を返します。

次に、サブスクリプトを呼んでみます。

1
2
3
4
5
6
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."

サブスクリプトは [] の前に ? を置きます。結果は先ほどと同じです。次に、値を初期化して試してみます。

1
2
3
4
5
6
7
8
9
10
11
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."

次に、何層も Optional が重なっている例です。

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

今、この john.residence は値を持っています。しかし、その residence が持っている address は nil なので、nil を返し、false になります。このように何層になっていても使えるので便利です。

1
2
3
4
5
6
7
8
9
10
11
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."

address に値を入れると true になります。

次にメソッドを見ていきます。

1
2
3
4
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."

このようにメソッドの括弧の後に ? を置き、返す値に対しても Optional Chaining を使用できます。