見出し画像

Swiftでプログラミング- Optional Chaining

Optional Chainingは、プロパティ、メソッド、および添え字を呼び出す過程でoptionalが nil であるかを判定します。optionalに値が含まれている場合は、プロパティ、メソッド、または添え字の呼び出しが成功し、optionalが nil の場合は、プロパティ、メソッド、または添え字によって nil を返します。複数の条件を連鎖させることができ、条件のいずれかのリンクが nil の場合、そこでnilを返します。。

Swiftでのオプションの連鎖は、Objective-Cでのnilのメッセージングに似ていますが、任意の型で動作する方法で、成功または失敗をチェックすることができます。

Optional Chaining as an Alternative to Forced Unwrapping   強制的アンラッピングの代替としてのOptional Chaining

Optional Chainingを指定するには、プロパティ、メソッド、またはサブスクリプトを呼び出す時にオプションがnilでない場合、プロパティ、メソッド、またはサブスクリプトを呼び出したい場合にオプション値の後にクエスチョンマーク(? )を付けます。これは、オプショナル値の後に感嘆符(!)を置いて、その値のアンラッピングを強制するのとよく似ています。主な違いは、オプショナルがnilの場合、Optional Chainingはそこでストップするのに対して、強制アンラッピングはオプショナルがnilの場合、ランタイムエラーが発生することです。

Optional Chaingにより、プロパティ、メソッド、または添え字がオプショナルの値がnilでないと返す場合でも、Optional Chainingの呼び出しの結果は常にオプショナル値です。このオプションの戻り値を使用して、Optional Chaining呼び出しが成功したか (返されたオプションに値が含まれている)、または連鎖の中に nil 値があるために成功しなかったか (返されたオプションの値が nil) を確認できます。

具体的には、Optional Chaining呼び出しの結果は、期待される戻り値と同じ型ですが、オプションでラップされています。通常、Intを返すプロパティは、Optional ChainingではInt?とします。

次のいくつかのコードは、Optional Chainingが強制的なアンラップとどのように異なり、成功を確認できるかを示しています。

まず、PersonとResidenceという2つのクラスが定義されています。

   class Person {
       var residence: Residence?
   }
   
   class Residence {
       var numberOfRooms = 1
   }

Residence インスタンスには、numberOfRoomsと呼ばれる単一のIntプロパティがあり、デフォルト値は1です。Personインスタンスには、Residence?型のOptionalなresidenceプロパティがあります。

新しいPersonインスタンスを作成する場合、そのresidenceプロパティはOptionalであるため、デフォルトでnilに初期化されます。 以下のコードでは、johnのresidenceプロパティ値はnilです。

let john = Person()

この人のresidenceのnumberOfRoomsプロパティにアクセスしようとすると、residenceの後に感嘆符(!)を置いてその値のアンラップを強制することで、ランタイムエラーが発生します。

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

上記のコードは、john.residenceがnil以外の値を持つ場合に成功し、roomCountを適切な部屋数を含むInt値に設定します。しかし、このコードはresidenceがnilの場合、上図のように常にランタイムエラーが発生してしまいます。

Optional Chainingは、numberOfRoomsの値にアクセスする別の方法でもできます。Optional Chainingを使用するには、感嘆符(!)の代わりにクエスチョンマーク(?)を使用します。

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

これはSwiftに対して、Optionalなresidenceプロパティを「連鎖」させ、residenceが存在する場合はnumberOfRoomsの値を取得するように指示しています。

numberOfRoomsへのアクセスの試みは失敗する可能性があるため、Optional Chainingの試みは、Int?または「optional Int」タイプの値を返します。上の例のようにresidenceがnilの場合、このOptional Intもnilとなり、numberOfRoomsへのアクセスができなかったことを反映します。Optional Intは、optional bindingによってアクセスされ、整数をアンラップして、非オプションの値をroomCount定数に割り当てます。

numberOfRoomsが非オプションのIntであっても、これは事実であることに注意してください。optional chainを介して照会されているということは、numberOfRoomsの呼び出しは常にIntではなくInt?となります。

john.residenceにResidenceインスタンスを割り当てて、nilの値を持たないようにすることができます。

john.residence = Residence()

john.residenceには、nilではなく実際のResidenceインスタンスが含まれるようになりました。 以前と同じOptional Chainingを使用してnumberOfRoomsにアクセスしようとすると、Int?が返されるようになりました。 これには、デフォルトのnumberOfRooms値1が含まれています。

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

Defining Model Classes for Optional Chaining

Optional Chainingを使用して、複数レベルの深さのプロパティ、メソッド、および添え字を呼び出すことができます。 これにより、相互に関連する型の複雑なモデル内のサブプロパティにドリルダウンし、それらのサブプロパティのプロパティ、メソッド、および添え字にアクセスできるかどうかを確認できます。

以下のコードスニペットは、マルチレベルのOptional Chainingの例を含む、後続のいくつかの例で使用する4つのモデルクラスを定義しています。 これらのクラスは、関連するプロパティ、メソッド、および添え字とともにRoom and Addressクラスを追加することにより、上からPerson andResidenceモデルを拡張します。

Personクラスは、以前と同じ方法で定義されます。

    class Person {
       var residence: Residence?
   }

Residenceクラスは前よりも複雑になっています。今回、Residenceクラスはroomsという変数プロパティを定義しており、これは[Room]型の空の配列で初期化されています。

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

このバージョンのResidenceはRoomインスタンスの配列を格納しているため、そのnumberOfRoomsプロパティは保存プロパティではなく計算プロパティとして実装されています。計算されたnumberOfRoomsプロパティは、単にrooms配列からcountプロパティの値を返します。

rooms 配列にアクセスするためのショートカットとして、このバージョンの Residence は、rooms 配列の要求されたインデックスの部屋にアクセスするための read-write 添え字を提供します。

また、このバージョンのResidenceは、printNumberOfRoomsというメソッドを提供しており、これはresidenceのroomsを単純に表示します。

最後に、Residence は address というオプションのプロパティを定義しており、そのタイプは Address? 。このプロパティのAddressクラスのタイプは以下のように定義されています。

rooms配列に使用されているRoomクラスは、nameという1つのプロパティと、そのプロパティに適切な部屋名を設定するイニシャライザを持つ、シンプルなクラスです。

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

このモデルの最後のクラスは「Address」です。このクラスには、Stringタイプの3つのオプションのプロパティがあります。最初の2つのプロパティ、buildingNameとbuildingNumberは、住所の一部として特定の建物を識別するための代替手段です。3つ目のプロパティであるstreetは、その住所の通りの名前に使われます。

    class Address {
       var buildingName: String?
       var buildingNumber: String?
       var street: String?
       func buildingIdentifier() -> String? {
           if let buildingNumber = buildingNumber, let street = street {
               return "\(buildingNumber) \(street)"
           } else if buildingName != nil {
               return buildingName
           } else {
               return nil
           }
       }
   }

Addressクラスには、buildingIdentifier()というメソッドがあり、戻り値の型はString? このメソッドは、住所のプロパティをチェックし、値がある場合は buildingName を、両方とも値がある場合は buildingNumber と street を連結したものを、それ以外は nil を返します。

Accessing Properties Through Optional Chaining

Optional Chainingを使用すると、オプション値のプロパティにアクセスし、そのプロパティへのアクセスが成功したかどうかをチェックすることができます。

上記で定義したクラスを使用して、新しい Person インスタンスを作成し、その numberOfRooms プロパティに以前のようにアクセスしてみます。

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

john.residenceがnilであるため、このOptional Chainingの呼び出しは以前と同じように失敗します。

Optional Chainingでプロパティの値を設定することもできます。

    let someAddress = Address()
   someAddress.buildingNumber = "29"
   someAddress.street = "Acacia Road"
   john.residence?.address = someAddress

この例では、john.residenceは現在"nil"なので、john.residenceのaddressプロパティを設定しようとしても失敗します。

代入はOptional Chainingの一部であり、"="演算子の右手側のコードは評価されません。前述の例では、someAddressが評価されないことは簡単にはわかりません。なぜなら、定数にアクセスしても何の副作用もないからです。下のリストでは、同じ代入を行っていますが、アドレスの作成には関数を使用しています。この関数は、値を返す前に「関数が呼ばれた」と表示しているので、"="演算子の右辺が評価されたかどうかを確認できます。

    func createAddress() -> Address {
       print("Function was called.")
       let someAddress = Address()
       someAddress.buildingNumber = "29"
       someAddress.street = "Acacia Road"
       return someAddress
   }
   john.residence?.address = createAddress()

何も表示されないことから、createAddress()関数が呼び出されていないことがわかります。

Calling Methods Through Optional Chaining

Optional Chainingを使用すると、オプショナル値のメソッドを呼び出し、そのメソッド呼び出しが成功したかどうかをチェックすることができます。これは、そのメソッドが戻り値を定義していない場合でも可能です。

Residence クラスの printNumberOfRooms() メソッドは、numberOfRooms の現在の値を表示します。このメソッドの外観は以下のとおりです。

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

このメソッドは、戻り値の型を指定していません。しかし、戻り値のない関数やメソッドは、「戻り値のない関数」で説明したように、暗黙のうちにVoidという戻り値の型を持っています。これは、() の値、または空のタプルを返すことを意味します。

このメソッドをOptional Chainingで呼び出した場合、メソッドの戻り値の型は Void ではなく Void? 。これにより、if文を使って、printNumberOfRooms()メソッドを呼び出すことができるかどうかをチェックすることができます。printNumberOfRoomsの戻り値をnilと比較して、メソッドの呼び出しが成功したかどうかを確認します。

    if john.residence?.printNumberOfRooms() != nil {
       print("It was possible to print the number of rooms.")
   } else {
       print("It was not possible to print the number of rooms.")
   }
   // Prints "It was not possible to print the number of rooms."

Optional Chainingを使用してプロパティを設定しようとした場合も同様です。 上記のオOptional Chainingによるプロパティへのアクセスの例では、residenceプロパティがnilであっても、john.residenceのアドレス値を設定しようとします。 オプションのチェーンを介してプロパティを設定しようとすると、Void?型の値が返されます。これにより、nilと比較して、プロパティが正常に設定されたかどうかを確認できます。

    if (john.residence?.address = someAddress) != nil {
       print("It was possible to set the address.")
   } else {
       print("It was not possible to set the address.")
   }
   // Prints "It was not possible to set the address."

Accessing Subscripts Through Optional Chaining   Optional Chainingを使った添え字へのアクセス

Optional Chainingを使用して、オプション値の添え字から値の取得と設定を試み、その添え字の呼び出しが成功したかどうかを確認できます。

Optional Chainingによってオプション値の添え字にアクセスするとき、疑問符は添え字の括弧の後ではなく前に置きます。オプション連結の疑問符は、常に式のオプション部分の直後に続きます。

以下の例では、Residence クラスに定義された添え字を使用して、john.residence プロパティの rooms 配列内の最初の部屋の名前を取得しようとしています。john.residence は現在 nil なので、添え字呼び出しは失敗します。

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

この添え字呼び出しのOptional Chainingの疑問符は、john.residence の直後、添え字の括弧の前に置かれています。これは、john.residence がOptional Chainingを試みるOptionalな値であるためです。

同様に、Optional Chainingを使用して、添え字を通して新しい値を設定しようとすることができます。

john.residence?[0] = Room(name: "Bathroom")

residence は現在 nil であるため、この添え字設定の試みも失敗します。

john.residence に実際の Residence インスタンスを作成して割り当て、その rooms 配列に 1 つ以上の Room インスタンスがある場合、Residence 添え字を使用して、Optional Chainingにより rooms 配列の実際の項目にアクセスできます。

   let johnsHouse = Residence()
   johnsHouse.rooms.append(Room(name: "Living Room"))
   johnsHouse.rooms.append(Room(name: "Kitchen"))
   john.residence = johnsHouse
   if let firstRoomName = john.residence?[0].name {
       print("The first room name is \(firstRoomName).")
   } else {
       print("Unable to retrieve the first room name.")
   }
   // Prints "The first room name is Living Room."

Accessing Subscripts of Optional Type

Swift の Dictionary 型の key 添え字のように、添え字が任意の型の値を返す場合は、添え字の閉じ括弧の後に疑問符を置き、その任意の戻り値に連鎖させます。

   var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
   testScores["Dave"]?[0] = 91
   testScores["Bev"]?[0] += 1
   testScores["Brian"]?[0] = 72
   // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

上の例では、testScoresという辞書を定義しています。この辞書には、StringキーをInt値の配列にマッピングする2つのキーと値のペアが含まれています。この例では、Optional Chainingを使用して、"Dave "配列の最初の項目を91に設定し、"Bev "配列の最初の項目を1だけ増やし、"Brian "をキーとする配列の最初の項目を設定しようとしています。最初の 2 つの呼び出しは成功します。testScores 辞書には "Dave" と "Bev" のキーが含まれているからです。3 番目の呼び出しは失敗します。なぜなら、testScores 辞書には "Brian" のキーが含まれていないからです。

Linking Multiple Levels of Chaining

複数のレベルのOptional Chainingをリンクして、モデルのより深いところにあるプロパティ、メソッド、およびサブスクリプトにドリルダウンすることができます。しかし、複数のレベルのOptional Chainingは、返される値のoptionalityのレベルを増やすものではありません。

別の言い方をすると

・取得しようとしている型がOptional でない場合、Optional Chainingによってオプションになります。
・取り出そうとしている型がすでにオプショナルである場合、連鎖によってオプショナル性が増すことはありません。

ですから。

・Optional ChainingでInt値を取得しようとすると、何段階の連鎖を使用しても、必ずInt?
・同様に、Optional ChainingでInt値を取得しようとすると、チェイニングのレベルがいくつになっても、常にIntが返されます。

以下の例では、johnのresidenceプロパティのaddressプロパティのstreetプロパティにアクセスしようとしています。ここでは2段階のオプショナル・チェイニングが使用されており、オプショナル型であるresidenceプロパティとaddressプロパティを連鎖させています。

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

john.residenceの値には、現在、有効なResidenceインスタンスが含まれています。しかし、john.residence.addressの値は現在nilです。このため、john.residence?.address?.streetの呼び出しは失敗します。

上記の例では、street プロパティの値を取得しようとしていることに注意してください。このプロパティの型は String? です。john.residence?.address?.streetの戻り値も、String?となります。基礎となるオプショナル型のプロパティに加えて、2段階のOptional Chainingが適用されています。

john.residence.addressの値として実際のAddressインスタンスを設定し、アドレスのstreetプロパティに実際の値を設定した場合、マルチレベル・Optional Chainingによってstreetプロパティの値にアクセスすることができます。

    let johnsAddress = Address()
   johnsAddress.buildingName = "The Larches"
   johnsAddress.street = "Laurel Street"
   john.residence?.address = johnsAddress
   if let johnsStreet = john.residence?.address?.street {
       print("John's street name is \(johnsStreet).")
   } else {
       print("Unable to retrieve the address.")
   }
   // Prints "John's street name is Laurel Street."

この例では、john.residenceの値に現在有効なResidenceインスタンスが含まれているため、john.residenceのaddressプロパティを設定しようとすると成功します。

Chaining on Methods with Optional Return Values

前述の例では、オプショナル型のプロパティの値をOptional Chainingで取得する方法を示しました。また、Optional Chainingを使用して、オプショナル型の値を返すメソッドを呼び出し、必要に応じてそのメソッドの戻り値に連鎖させることもできます。

以下の例では、Optional Chainingによって Address クラスの buildingIdentifier() メソッドを呼び出しています。このメソッドが返す値はString? であり、上述のとおり、Optional Chainingを経たこのメソッド呼び出しの最終的な戻り値の型もString?となります。

   if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
       print("John's building identifier is \(buildingIdentifier).")
   }
   // Prints "John's building identifier is The Larches."

このメソッドの戻り値に対してさらにOptional Chainingを行いたい場合は、メソッドの括弧の後にOptional Chainingのクエスチョンマークを入れます。

    if let beginsWithThe =
       john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
       if beginsWithThe {
           print("John's building identifier begins with \"The\".")
       } else {
           print("John's building identifier doesn't begin with \"The\".")
       }
   }
   // Prints "John's building identifier begins with "The"."
上記の例では、Optional Chainingの疑問符を括弧の後に置いています。これは、連鎖しているオプションの値が buildingIdentifier() メソッドの戻り値であり、buildingIdentifier() メソッド自体ではないためです。

















この記事が気に入ったらサポートをしてみませんか?