オプショナルの連鎖

オプショナルの連鎖は、現在nilが存在している可能性があるオプショナルプロパティ、メソッド、およびサブスクリプトを照会および呼び出すためのプロセスです。オプショナルに値が含まれている場合、プロパティ、メソッド、またはサブスクリプトの呼び出しは成功します。オプショナルがnilの場合、プロパティ、メソッド、またはサブスクリプトの呼び出しはnilを返します。複数のクエリを連鎖することができ、連鎖内のリンクがnilである場合、連鎖全体が正常に失敗します。

注意

Swiftでのオプショナルの連鎖はObjective-Cでのnilメッセージングに似ていますが、どのタイプでも機能し、成功または失敗を確認できるようになっています。

強制アンラップの代替としてのオプショナルの連鎖

オプショナルがnil以外の場合、プロパティ、メソッド、またはサブスクリプトを呼び出すオプショナル値の後に疑問符(?)を配置して、オプショナル連鎖を指定します。これは、オプショナル値の後に感嘆符(!)を配置して、その値を強制的にアンラップするのとよく似ています。主な違いは、オプショナルがnilの場合はオプショナルの連鎖が正常に失敗するのに対し、オプショナルがnilの場合は強制アンラップが実行時エラーをトリガーすることです。

nil値に対してオプショナルの連鎖を呼び出すことができるという事実を反映するために、オプショナルの連鎖呼び出しの結果は、クエリしているプロパティ、メソッド、またはサブスクリプトが非オプショナル値を返した場合でも、常にオプショナル値になります。このオプショナル戻り値を使用して、オプショナルの連鎖呼び出しが成功した(返されたオプショナルに値が含まれている)かnil、連鎖内の値が原因で成功しなかったか(返されたオプショナル値はnil)を確認できます。

具体的には、オプショナルの連鎖呼び出しの結果は、予期される戻り値と同じ型ですが、オプショナルでラップされます。通常はIntを返すプロパティは、オプショナルの連鎖を通じてアクセスするとInt?を返します。

次のいくつかのコードスニペットは、オプショナル連鎖が強制アンラップとどのように異なるかを示し、成功を確認できるようにします。

まず、2つのクラス Person とResidence を定義します。

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

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

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

let john = Person()

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

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

上記のコードjohn.residenceは、非nil値の場合に成功し、roomCountを適切な数の部屋を含むInt値に設定します。ただし、上記のように、このコードは常にresidenceがnilのときにランタイムエラーをトリガーします。

オプショナルの連鎖は、numberOfRoomsの値にアクセスするための代替方法を提供します。オプショナル連鎖を使用するには、感嘆符の代わりに疑問符を使用します。

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にオプショナルresidenceプロパティを「連鎖」し、residencegが存在する場合はnumberOfRoomsの値を取得するように指示します。

numberOfRoomsへのアクセスの試みは失敗する可能性があるため、オプショナル連鎖の試みはInt?型の値または「optional Int」を返します。residenceがnilのとき、上記の例のように、それがnumberOfRoomsにアクセスすることができなかったという事実を反映して、このオプショナルIntはnilになります。オプショナルIntは、オプショナルバインディングを介してアクセスされ、整数をアンラップして、非オプショナルの値をroomCount変数に割り当てます。

これはnumberOfRoomsが非オプショナルIntでも、これは事実です。オオプショナルの連鎖を通じて照会されるという事実は、numberOfRoomsへの呼び出しが常にのInt?代わりにIntを返すことを意味します。

Residenceインスタンスをjohn.residenceに割り当てると、nil値ではなくなります。

john.residence = Residence()

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

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)."

オプショナル連鎖のためのモデルクラスの定義

複数レベルの深さのプロパティ、メソッド、およびサブスクリプトの呼び出しでオプショナルの連鎖を使用できます。これにより、相互に関連するタイプの複雑なモデル内のサブプロパティにドリルダウンし、それらのサブプロパティのプロパティ、メソッド、およびサブスクリプトにアクセスできるかどうかを確認できます。

以下のコードスニペットは、マルチレベルのオプショナル連鎖の例を含む、後続のいくつかの例で使用する4つのモデルクラスを定義します。これらのクラスは、関連するプロパティ、メソッド、およびサブスクリプトとともに、RoomおよびAddressクラスを追加することにより、上記のPersonおよびResidenceモデルを拡張します。

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?
}

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

rooms配列にアクセスするためのショートカットとして、このResidenceバージョンのは、rooms配列内の要求されたインデックスにある部屋へのアクセスを提供する読み書きサブスクリプトが用意されています。

このバージョンのResidenceには、printNumberOfRoomsと呼ばれるメソッドも用意されています。これは、住宅の部屋数を出力するだけです。

最後に、ResidenceというAddress?型のaddressと呼ばれるオプショナルプロパティを定義します。このプロパティのAddressクラスタイプは以下に定義されています。

rooms配列のために使用されるRoomクラスは、nameプロパティを持つ単純なクラスであり、そのプロパティに適切なルーム名を設定するイニシャライザ:

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

このモデルの最後のクラスはAddressと呼ばれます。このクラスには、String?型の3つのオプショナルプロパティがあります。最初の2つのプロパティbuildingNamebuildingNumberは、住所の一部として特定の建物を識別するための代替方法です。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 を返します。

オプショナル連鎖によるプロパティへのアクセス

強制アンラップの代替としてのオプショナルの連鎖示されているように、オプショナルの連鎖使用して、オプショナル値のプロパティにアクセスし、そのプロパティアクセスが成功したかどうかを確認できます。

上記で定義したクラスを使用して新しい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 であり、このオプショナル連鎖コールは、前と同じように失敗します。

オプショナルの連鎖を通じてプロパティの値を設定することもできます。

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

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

割り当てはオプショナル連鎖の一部です。つまり、= 演算子の右側のコードは評価されません。前の例では、定数にアクセスしても副作用がないため、someAddressが評価されないことが簡単にわかりません。以下のリストは同じ割り当てを行っていますが、関数を使用してアドレスを作成しています。この関数は、値を返す前に「Function was called」を出力します。これにより、=演算子の右側が評価されたかどうかを確認できます。

func createAddress() -> Address {
    print("Function was called.")

    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"

    return someAddress
}
john.residence?.address = createAddress()

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

オプショナル連鎖によるメソッドの呼び出し

オプショナルの連鎖を使用して、オプショナル値でメソッドを呼び出し、そのメソッドの呼び出しが成功したかどうかを確認できます。そのメソッドが戻り値を定義していない場合でも、これを行うことができます。

ResidenceクラスのprintNumberOfRooms()メソッドは、numberOfRoomsの現在の値を出力します。メソッドは次のようになります。

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

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

If you call this method on an optional value with optional chaining, the method’s return type will be Void?, not Void, because return values are always of an optional type when called through optional chaining. This enables you to use an if statement to check whether it was possible to call the printNumberOfRooms() method, even though the method does not itself define a return value. Compare the return value from the printNumberOfRooms call against nil to see if the method call was successful:

オプショナルの連鎖を使用してオプショナル値でこのメソッドを呼び出す場合、メソッドの戻り値の型は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."

オプショナルの連鎖を通じてプロパティを設定しようとした場合も同様です。上記のオプショナルの連鎖によるプロパティへのアクセスの例では、residenceプロパティがnilであってもjohn.residenceにaddress値を設定しようとしています。オプショナルの連鎖を介してプロパティを設定しようとすると、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."

オプショナル連鎖によるサブスクリプトのアクセス

オプショナル連鎖を使用して、オプショナル値のサブスクリプトから値を取得および設定し、そのサブスクリプトの呼び出しが成功したかどうかを確認できます。

注意

オプショナル連鎖を介してオプショナル値のサブスクリプトにアクセスする場合、疑問符をサブスクリプトの大括弧の後ではなく前に置きます。オプショナル連鎖の疑問符は常に、オプショナルの式の部分の直後に続きます。

以下の例では、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."

このサブスクリプト呼び出しのオプショナル連鎖疑問符は、john.residenceの直後、サブスクリプト括弧の前に配置されます。これは、john.residenceがオプショナル連鎖が試行されるオプショナル値であるためです。

同様に、オプショナル連鎖を伴うサブスクリプトを使用して、新しい値を設定することができます。

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

現在residenceは、nilであるため、このサブスクリプト設定の試行も失敗します。

実際のResidenceインスタンスを作成してjohn.residenceに割り当て、そのrooms配列に1つ以上のRoomインスタンスがある場合、Residenceサブスクリプトを使用して、オプショナルの連鎖を通じて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."

オプショナルタイプのサブスクリプトへのアクセス

サブスクリプトがオプショナルタイプの値(SwiftのDictionaryタイプのキーサブスクリプトなど)を返す場合は、サブスクリプトの閉じ括弧の後に疑問符を付けて、オプショナル戻り値を連鎖します。

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つのキーと値のペアが含まれています。この例では、オプショナル連鎖を使用して、"Dave"配列の最初の項目を91に設定しています。"Bev"配列の最初の項目を1;増分します。そして、配列の最初の項目を”Brian”のキーに設定しようとします。ので、最初の2つの呼び出しが、testScores辞書が"Dave""Bev"のキーが含まれているため、成功する。testScores辞書には”Brian”のキーが含まれていないため、3番目の呼び出しは失敗します。

複数レベルの連鎖のリンク

オプショナル連鎖の複数のレベルをリンクして、モデル内のより深いプロパティ、メソッド、およびサブスクリプトにドリルダウンできます。ただし、オプショナル連鎖の複数のレベルは、戻り値にオプショナルレベルを追加しません。

別の言い方をすると:

  • 取得しようとしている型がオプショナルではない場合、オプショナル連鎖のためにオプショナルになります。
  • あなたが取得しようとしている型がすでにオプショナルの場合はそれは連鎖のため、オプショナル以上にはなりません。

したがって:

  • オプショナル連鎖を通じてInt値を取得しようとすると、使用されている連鎖のレベルの数に関係なく、常にInt?が返されます。
  • 同様に、オプショナルの連鎖を通じてInt?値を取得しようとすると、使用される連鎖のレベルの数に関係なく、常にInt?が返されます。

以下の例では、johnのresidenceのプロパティのaddressのプロパティのstreetのプロパティをアクセスします。ここでは、どちらもオプショナル型のresidenceおよびaddressのプロパティを連鎖する、オプショナル連鎖の2つのレベルがあります:

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?です。したがって、プロパティの基になるオプショナルタイプに加えて、2つのレベルのオプショナルの連鎖が適用されていても、john.residence?.address?.streetの戻り値はString?です。

実際のAddressインスタンスをjohn.residence.addressの値として設定し、アドレスのstreetプロパティに実際の値を設定すると、マルチレベルのオプショナルの連鎖を通じて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のaddressプロパティを設定する試みは成功します。これは、john.residenceの値に現在有効なResidenceインスタンスが含まれているためです。

オプショナル戻り値を持つメソッドの連鎖

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

以下の例では、オプショナル連鎖を通じてAddressクラスのbuildingIdentifier()メソッドを呼び出しています。このメソッドは、String?型の値を返します。上記のように、オプショナルの連鎖後のこのメソッド呼び出しの最終的な戻り値の型も次のとおりString?です。

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

このメソッドの戻り値に対してさらにオプショナルの連鎖を実行する場合は、メソッドの括弧の後にオプショナルの連鎖の疑問符を配置します。

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 does not begin with \"The\".")
    }
}
// Prints "John's building identifier begins with "The"."

注意

上記の例では、オプショナル連鎖の疑問符を括弧の後に配置しています。これは、連鎖するオプショナル値がbuildingIdentifier()メソッドの戻り値であり、buildingIdentifier()メソッド自体ではないためです。

投稿者: admin

Free Software Engineer

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です