Swift オプショナルチェーン

オプショナルチェーン(Optional Chaining)は、プロパティ、メソッド、サブスクリプトを要求および呼び出すことができるプロセスであり、要求または呼び出しの対象がnilである可能性があります。

オプショナルチェーンは2つの値を返します:

  • ターゲットに値があれば、呼び出しは成功し、その値を返します。

  • ターゲットがnilの場合、呼び出しはnilを返します。

複数の要求や呼び出しを1つのチェーンに連結でき、いずれかのノードがnilの場合、チェーン全体が無効になります。


オプショナルチェーンは強制アンラップの代替となる

プロパティ、メソッド、またはサブスクリプトのオプショナル値の後に疑問符(?)を置くことで、オプショナルチェーンを定義できます。

オプショナルチェーン '?' 感嘆符(!)でメソッド、プロパティ、サブスクリプトのオプショナルチェーンを強制展開する
? はオプショナル値の後に置いて、メソッド、プロパティ、サブスクリプトを呼び出します。 ! はオプショナル値の後に置いて、メソッド、プロパティ、サブスクリプトを呼び出し、値を強制展開します。
オプショナルがnilの場合、より親切なエラーメッセージを出力します。 オプショナルがnilの場合、強制展開はエラーを引き起こします。

感嘆符(!)を使ったオプショナルチェーンの例

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

//将导致运行时错误
let roomCount = john.residence!.numberOfRooms

上記のプログラムを実行すると、出力結果は次のようになります:

fatal error: unexpectedly found nil while unwrapping an Optional value

感嘆符(!)で強制アンラップして、この人のresidenceプロパティのnumberOfRoomsプロパティ値を取得しようとすると、実行時エラーが発生します。なぜなら、この時点ではアンラップできるresidence値が存在しないからです。

疑問符(?)を使ったオプショナルチェーンの例

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

// 链接可选residence?属性,如果residence存在则取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
    print("John 的房间号为 \(roomCount)。")
} else {
    print("不能查看房间号")
}

上記のプログラムを実行すると、出力結果は次のようになります:

部屋番号を表示できません

このnumberOfRoomsを取得しようとする操作は失敗する可能性があるため、オプショナルチェーンはInt?型の値、または「オプショナルInt」と呼ばれる値を返します。residenceが空の場合(上記の例)、オプショナルIntはnilになり、numberOfRoomsにアクセスできない状態になります。

注意すべき点は、numberOfRoomsが非オプショナルのInt(Int?)であっても、このことは成り立ちます。オプショナルチェーンを通じた要求であれば、最終的にnumberOfRoomsは常にInt?を返し、Intではなくなります。


オプショナルチェーンのためのモデルクラスを定義する

オプショナルチェーンを使用して、プロパティ、メソッド、サブスクリプトを複数階層にわたって呼び出すことができます。これにより、それらの間の複雑なモデルを利用して、より下位のプロパティを取得し、そのような下位プロパティを正常に取得できるかどうかを確認できます。

例

複数レベルのオプショナルチェーンを含む4つのモデルクラスを定義します:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

オプショナルチェーンによるメソッドの呼び出し

オプショナルチェーンを使用して、オプショナル値のメソッドを呼び出し、メソッド呼び出しが成功したかどうかを確認できます。このメソッドが戻り値を持たない場合でも、オプショナルチェーンを使用してこの目的を達成できます。

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()


if ((john.residence?.printNumberOfRooms()) != nil) {
    print("输出房间号")
} else {
    print("无法输出房间号")
}

上記のプログラムを実行すると、出力結果は次のようになります:

部屋番号を出力できません

if文を使用して、printNumberOfRoomsメソッドを正常に呼び出せるかどうかを確認します:メソッドがオプショナルチェーンを通じて正常に呼び出された場合、printNumberOfRoomsの暗黙的な戻り値はVoidになり、成功しなかった場合はnilを返します。


オプショナルチェーンを使ったサブスクリプトの呼び出し

オプショナルチェーンを使用して、サブスクリプトから値を取得し、サブスクリプトの呼び出しが成功したかどうかを確認できます。ただし、オプショナルチェーンを使用してサブスクリプトを設定することはできません。

例1

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()
if let firstRoomName = john.residence?[0].name {
    print("第一个房间名 \(firstRoomName).")
} else {
    print("无法检索到房间")
}

上記のプログラムを実行すると、出力結果は次のようになります:

部屋を検索できません

サブスクリプト呼び出しでは、オプショナルチェーンの疑問符は、サブスクリプトの括弧の前に、john.residenceの直後に置かれます。john.residenceはオプショナルチェーンが取得しようとしているオプショナル値だからです。

例2

例では、john.residenceにResidenceインスタンスを作成し、そのrooms配列に1つ以上のRoomインスタンスがある場合、オプショナルチェーンを使用してResidenceサブスクリプトを通じてrooms配列内のインスタンスを取得できます:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence!.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
    print("John 所在的街道是 \(johnsStreet)。")
} else {
    print("无法检索到地址。 ")
}

上記のプログラムを実行すると、出力結果は次のようになります:

John 所在的街道是 Laurel Street。

オプショナルチェーン呼び出しによるサブスクリプトへのアクセス

オプショナルチェーン呼び出しを使用すると、サブスクリプトを使ってオプショナル値を読み書きでき、サブスクリプト呼び出しが成功したかどうかを判断できます。

例

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("第一个房间名为\(firstRoomName)")
} else {
    print("无法检索到房间")
}

上記のプログラムを実行すると、出力結果は次のようになります:

最初の部屋の名前はリビングルームです

オプショナル型のサブスクリプトにアクセスする

サブスクリプトがオプショナル型の値を返す場合(SwiftのDictionaryのキーサブスクリプトなど)、サブスクリプトの閉じ括弧の後に疑問符を置いて、サブスクリプトのオプショナルな戻り値をチェーンできます:

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

上記の例では、testScores配列を定義し、2つのキーと値のペアを含み、String型のキーを整数配列にマッピングしています。

この例では、オプショナルチェーン呼び出しを使用して、"Dave"配列の最初の要素を91に設定し、"Bev"配列の最初の要素に+1し、次に"Brian"配列の最初の要素を72に設定しようとします。

最初の2つの呼び出しは成功します。これらの2つのキーが存在するからです。しかし、キー"Brian"は辞書に存在しないため、3番目の呼び出しは失敗します。


複数レベルのチェーンを連結する

複数レベルのオプショナルチェーンを連結して、モデル内のより下層のプロパティ、メソッド、サブスクリプトを掘り下げることができます。ただし、複数レベルのオプショナルチェーンは、すでに返されたオプショナル値よりも多くのレベルを追加することはできません。

オプショナルチェーンを通じてInt値を取得しようとする場合、何階層のリンクを使用しても返される値は常にInt?です。同様に、オプショナルチェーンを通じてInt?値を取得しようとする場合も、何階層のリンクを使用しても返される値は常にInt?です。

例1

次の例では、johnのresidenceプロパティ内のaddressのstreetプロパティを取得しようとしています。ここでは、residenceとaddressプロパティ(両方ともオプショナル型)を結びつけるために2レベルのオプショナルチェーンを使用しています:

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()

if let johnsStreet = john.residence?.address?.street {
    print("John 的地址为 \(johnsStreet).")
} else {
    print("不能检索地址")
}

上記のプログラムを実行すると、出力結果は次のようになります:

住所を検索できません

例2

Addressのインスタンスをjohn.residence.addressの値として設定し、addressのstreetプロパティに実際の値を設定すると、複数レベルのオプショナルチェーンを通じてこのプロパティ値を取得できます。

class Person {
   var residence: Residence?
}

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("房间号为 \(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 != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}
let john = Person()
john.residence?[0] = Room(name: "浴室")

let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
    print("第一个房间是\(firstRoomName)")
} else {
    print("无法检索房间")
}

上記の例の出力結果は次のようになります:

最初の部屋はリビングルームです

オプショナル値を返す関数へのチェーン

オプショナルチェーンを使用して、オプショナル値を返すメソッドを呼び出すこともでき、オプショナル値に対してさらにチェーンを続けることができます。

例

class Person {
    var residence: Residence?
}

// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        return rooms[i]
    }
    func printNumberOfRooms() {
        print("房间号为 \(numberOfRooms)")
    }
    var address: Address?
}

// Room 定义一个name属性和一个设定room名的初始化器
class Room {
    let name: String
    init(name: String) { self.name = name }
}

// 模型中的最终类叫做Address
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if (buildingName != nil) {
            return buildingName
        } else if (buildingNumber != nil) {
            return buildingNumber
        } else {
            return nil
        }
    }
}

let john = Person()

if john.residence?.printNumberOfRooms() != nil {
    print("指定了房间号)")
}  else {
    print("未指定房间号")
}

上記のプログラムを実行すると、出力結果は次のようになります:

部屋番号が指定されていません
その他の拡張