【Swift】Appleの新言語「Swift」のリファレンスを読む(13) - Optional Chaining
- (1) - The Basic
- (2) - Basic Operators、Strings and Characters
- (3) - Collection Types
- (4) - Control Flow
- (5) - Functions
- (6) - Closures、Enumerations
- (7) - Classes and Structures
- (8) - Properties
- (9) - Methods、Subscripts
- (10) - Inheritance
- (11) - Initialization
- (12) - Deinitialization、Automatic Reference Counting
- (13) - Optional Chaining
- (14) - Type Casting、Nested Types
- (15) - Extensions
- (16) - Protocols
- (17) - Generics
- (18) - Advanced Operators
- (19) - Dynamic Type、Attribute、Special Literals
注意
- あくまでメモ書きなので細かい部分を端折りますし、色々間違ってるかもしれません。ちゃんとした内容は原文を読んでね。
- コード例は基本的に原文からそのまま引用していますが、ちょっとした注釈をつけたり、統合したりしています。
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が設定できることに意義があるのではなく、元々こう言う使い方をするものだと思います。