Optional chaining : nil 일 수도 있는 것에 질의하고, property, method, subscript 를 호출하는 것

여러 query 는 chaining 돼서 하나의 chain 이라도 nil 인 경우 전체 chain 이 우아하게 실패하게 됨

Optional Chaining as an Alternative to Forced Unwrapping

Optional 값 뒤에 question mark ? 를 붙여서 optional chaining 을 명시. 실패해도 우아하게 실패가 남

Force unwrapping : exclamation point ! 를 optional 값 뒤에 붙여서 값을 강제로 unwrapping 하는 것. 실패할 경우 runtime error 가 발생함

Optional Chaining 이 nil 에 호출될 수 있기 때문에 결과는 항상 optional 값임.

class Person {
    var residence: Residence?
}


class Residence {
    var numberOfRooms = 1
}

let john = Person()

let roomCount = john.residence!.numberOfRooms // force unwrapping
// ❗️ this triggers a runtime error

// optional chaining 을 통해 optional residence property 에 chain 을 하고 값이 존재하는 경우 값을 불러오도록 함
// roomCount : Int? type
// optional binding 을 통해 값 unwrap
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."

Defining Model Classes for Optional Chaining

Property, method, subscript 호출을 optional chaining 과 사용할 수 있음.

Multilevel optional chaining 예시

class Person {
    var residence: Residence?
}

class Residence {
    var rooms: [Room] = []
    
    // computed property
    var numberOfRooms: Int {
        return rooms.count
    }
    
    // read-write subscript
    subscript(i: Int) -> Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }
    
    func printNumberOfRooms() {
        print("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 let buildingNumber = buildingNumber, let street = street {
            return "\(buildingNumber) \(street)"
        } else if buildingName != nil {
            return buildingName
        } else {
            return nil
        }
    }
}

Accessing Properties Through Optional Chaining

위 코드에 이어서.

Optional chaining 을 사용해서 optional value 의 property 에 접근할 수 있고 성공했는지 확인 가능.

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


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

// Optional chaing 을 사용해서 property 의 값을 설정할 수도 있음
// 이 경우 john.residence 가 nil 이기 때문에 값을 설정하는게 fail 남
john.residence?.address = someAddress
// ❗️Assignment 도 optional chaining 의 일부로, = operator 다음의 코드는 evaluated 되지 않음.

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


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


    return someAddress
}
// ❗️optional chaining 의 결과가 nil 이기 때문에 createAddress() 는 실행되지 않고, 아무것도 print 되지 않음.
john.residence?.address = createAddress()

Calling Methods Through Optional Chaining

// 암묵적으로 Void 를 return
func printNumberOfRooms() {
    print("The number of rooms is \(numberOfRooms)")
}

// Void? 를 return
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 을 통해 값을 할당하려는 시도는 Void? 를 return 하기 때문에 값이 성공적으로 할당됐는지를 확인하기 위해 쓸 수 있음
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 value 의 subscript 를 사용할 경우 대괄호 전에 question mark 를 붙임. Optional chaining 의 question mark 는 항상 optional 인 expression 직후에 붙음

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 가 nil 이기 때문에 할당이 실패함
john.residence?[0] = Room(name: "Bathroom")

Accessing Subscripts of Optional Type

Subscript 가 optional type 을 return 하는 경우 대괄호 뒤에 question mark 위치

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]

Linking Multiple Levels of Chaining

Multiple level optional chaining 을 묶어도 return 되는 값의 optionality 에 level 을 추가하지는 않음.

e.g.

// String?
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."

Chaining on Methods with Optional Return Values

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

// buildingIdentifier() 가 String? 을 return 하기 때문에 ? 를 붙이지 만약 String 을 return한다면 ? 를 안붙임
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"."