Optional Chaining

Groot·2022년 8월 24일
0

Swift Language Guide

목록 보기
15/24
post-thumbnail

Optional Chaining

  • 옵셔널 체이닝은 현재 nil일 수 있는 옵셔널에 대한 프로퍼티, 메서드 및 첨자를 쿼리하고 호출하는 프로세스입니다.
  • 옵셔널에 값이 포함되어 있으면 프로퍼티, 메서드 또는 서브 스크립트 호출이 성공합니다.
  • 선택 사항이 nil이면 프로퍼티, 메서드 또는 서브 스크립트 호출은 nil을 반환합니다.
  • 여러 쿼리를 함께 연결할 수 있으며 체인의 링크가 nil인 경우 전체 체인이 정상적으로 실패합니다.

    Swift의 옵셔널 체이닝은 Objective-C의 nil 메시지와 유사하지만 모든 유형에서 작동하며 성공 또는 실패를 확인할 수 있습니다.

📌 Optional Chaining as an Alternative to Forced Unwrapping

  • 옵셔널이 nil이 아닌 경우 프로퍼티, 메서드 또는 서브 스크립트를 호출하려는 옵셔널 값 뒤에 물음표(?)를 배치하여 옵셔널 체이닝을 지정합니다.

  • 이는 값을 강제로 풀기 위해 옵셔널 값 뒤에 느낌표(!)를 배치하는 것과 매우 유사합니다.

  • 주요 차이점은 옵셔널이 nil일 때 옵셔널 체이닝이 정상적으로 실패하는 반면, 옵셔널이 nil일 때 강제 언래핑은 런타임 오류를 트리거한다는 것입니다.

  • 옵셔널 체이닝이 nil 값에 대해 호출될 수 있다는 사실을 반영하기 위해 옵셔널 체이닝 호출의 결과는 쿼리하는 프로퍼티, 메서드 또는 서브 스크립트가 옵셔널이 아닌 값을 반환하더라도 항상 옵셔널 값입니다.

  • 이 옵셔널 반환 값을 사용하여 옵셔널 체이닝 호출이 성공했는지(반환된 옵셔널 값에 값이 포함됨) 체인의 nil 값으로 인해 성공하지 못한지(반환된 옵셔널 값은 nil임) 확인할 수 있습니다.

  • 특히, 옵셔널 체이닝 호출의 결과는 예상되는 반환 값과 동일한 유형이지만 옵셔널으로 래핑됩니다.

  • 일반적으로 Int를 반환하는 프로퍼티은 Int? 옵셔널 체이닝을 통해 액세스할 때.

  • 다음 몇 가지 코드 조각은 옵셔널 체이닝이 강제 래핑 해제와 어떻게 다른지 보여주고 성공 여부를 확인할 수 있도록 합니다.

    class Person {
        var residence: Residence?
    }
    
    class Residence {
        var numberOfRooms = 1
    }
  • Residence 인스턴스에는 numberOfRooms라는 단일 Int property이 있으며 기본값은 1입니다.

  • Person 인스턴스에는 Residence? 유형의 옵셔널 property property 있습니다.

  • 새 Person 인스턴스를 만드는 경우 해당 거주 프로퍼티은 선택 사항이기 때문에 기본적으로 nil로 초기화됩니다.

  • 아래 코드에서 john의 residence property 값은 nil입니다.

    let john = Person()
  • 이 사람의 residenced의 numberOfRoom 프로퍼티에 액세스하려고 하면residenced 뒤에 느낌표를 배치하여 해당 값의 래핑 해제를 강제 실행하면 래핑 해제할 residenced 값이 없기 때문에 런타임 오류가 발생합니다.

    let roomCount = john.residence!.numberOfRooms
    // this triggers a runtime error
  • 위의 코드는 john.residence가 nil이 아닌 값을 가질 때 성공하고 roomCount를 적절한 방 수를 포함하는 Int 값으로 설정합니다. 그러나 이 코드는 위에서 설명한 것처럼 거주가 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."
  • 이것은 옵셔널인 거주 프로퍼티에 대해 "연결"하고 거주가 존재하는 경우 numbderOfRooms의 값을 검색하도록 Swift에 지시합니다.

  • numberOfRooms에 액세스하려는 시도는 실패할 가능성이 있으므로 옵셔널 체이닝 시도는 Int? 또는 "옵셔널 Int" 유형의 값을 반환합니다.

  • 위의 예에서와 같이 거주지가 nil인 경우 numberOfRooms에 액세스할 수 없다는 사실을 반영하기 위해 이 옵셔널 Int도 nil이 됩니다.

  • 옵셔널 Int는 옵셔널 바인딩을 통해 액세스하여 정수를 래핑하고 roomCount 상수에 옵셔널이 아닌 값을 할당합니다.

  • numberOfRooms가 옵셔널 Int가 아닌 경우에도 마찬가지입니다.

  • 옵셔널 체인을 통해 쿼리된다는 사실은 numberOfRooms에 대한 호출이 항상 Int? 대신에 Int를 리턴한다는걸 의미한다.

  • Residence 인스턴스를 john.residence에 할당하여 더 이상 nil 값을 갖지 않도록 할 수 있습니다.

    john.residence = Residence()
  • john.residence는 이제 nil이 아닌 실제 Residence 인스턴스를 포함합니다. 이전과 동일한 옵셔널 체이닝로 numberOfRoom에 액세스하려고 하면 이제 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

  • 한 수준 이상 깊이 있는 프로퍼티, 메서드 및 첨자에 대한 호출과 함께 옵셔널 체이닝을 사용할 수 있습니다.
  • 이를 통해 상호 관련된 유형의 복잡한 모델 내에서 하위 프로퍼티을 드릴다운하고 해당 하위 프로퍼티의 프로퍼티, 메서드 및 첨자에 액세스할 수 있는지 여부를 확인할 수 있습니다.
  • 아래 코드 조각은 다중 수준 옵셔널 체이닝의 예를 포함하여 여러 후속 예에서 사용할 4가지 모델 클래스를 정의합니다.
  • 이러한 클래스는 연결된 프로퍼티, 메서드 및 첨자와 함께 Room 및 Address 클래스를 추가하여 위에서부터 Person 및 Residence 모델을 확장합니다.
  • Person 클래스는 이전과 같은 방식으로 정의됩니다.
    class Person {
        var residence: Residence?
    }
  • 레지던스 클래스는 이전보다 더 복잡합니다. 이번에는 Residence 클래스가 [Room] 유형의 빈 배열로 초기화되는 rooms이라는 변수 프로퍼티을 정의합니다.
    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?
    }
  • 이 레지던스 버전은 Room 인스턴스의 배열을 저장하기 때문에 numberOfRooms 프로퍼티은 저장 프로퍼티이 아닌 계산 프로퍼티으로 구현됩니다.
  • 계산된 numberOfRooms 프로퍼티은 단순히 rooms 배열에서 count 프로퍼티 값을 반환합니다.
  • rooms 배열에 액세스하는 지름길로, 이 레지던스 버전은 rooms 배열의 요청된 인덱스에 있는 방에 대한 액세스를 제공하는 읽기-쓰기 첨자를 제공합니다.
  • 이 버전의 Residence는 단순히 거주지의 방 수를 인쇄하는 printNumberOfRooms라는 메소드도 제공합니다.
  • 마지막으로 Residence는 유형이 Address?인 address라는 옵셔널 프로퍼티을 정의합니다. 이 프로퍼티의 주소 클래스 유형은 아래에 정의되어 있습니다.
  • rooms 배열에 사용되는 Room 클래스는 name이라는 프로퍼티 하나와 해당 프로퍼티을 적절한 방 이름으로 설정하는 초기화 프로그램이 있는 간단한 클래스입니다.
    class Room {
        let name: String
        init(name: String) { self.name = name }
    }
  • 이 모델의 마지막 클래스를 주소라고 합니다. 이 클래스에는 String? 유형의 세 가지 옵셔널 프로퍼티이 있습니다.
  • 처음 두 프로퍼티인 buildingName 및 buildingNumber는 특정 건물을 주소의 일부로 식별하는 대체 방법입니다.
  • 세 번째 프로퍼티인 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 클래스는 반환 유형이 String?인 buildingIdentifier()라는 메서드도 제공합니다.
  • 이 메서드는 주소의 프로퍼티을 확인하고 값이 있는 경우 buildingName을 반환하고 둘 다 값이 있는 경우 거리와 연결된 buildingNumber를 반환하고 그렇지 않은 경우 nil을 반환합니다.

📌 Accessing Properties Through Optional Chaining

  • Forced Unwrapping의 대안으로 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이기 때문에 이 옵셔널 체이닝 호출은 이전과 같은 방식으로 실패합니다.

  • 옵셔널 체이닝을 통해 프로퍼티 값을 설정하려고 시도할 수도 있습니다.

    let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"
    john.residence?.address = someAddress
  • 이 예에서 john.residence가 현재 nil이기 때문에 john.residence의 주소 프로퍼티을 설정하려는 시도는 실패합니다.

  • 할당은 옵셔널 체이닝의 일부입니다. 즉, = 연산자의 오른쪽에 있는 코드는 실행되지 않습니다.

  • 앞의 예에서 상수에 접근하면 부작용이 없기 때문에 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() 함수가 호출되지 않았음을 알 수 있습니다.

📌 Calling Methods Through Optional Chaining

  • 옵셔널 체이닝을 사용하여 옵셔널 값에 대한 메서드를 호출하고 해당 메서드 호출이 성공했는지 확인할 수 있습니다.
  • 해당 메서드가 반환 값을 정의하지 않더라도 이 작업을 수행할 수 있습니다.
  • Residence 클래스의 printNumberOfRooms() 메서드는 numberOfRooms의 현재 값을 인쇄합니다.
  • 방법은 다음과 같습니다.
    func printNumberOfRooms() {
        print("The number of rooms is \(numberOfRooms)")
    }
  • 이 메서드는 반환 유형을 지정하지 않습니다. 그러나 반환 유형이 없는 함수 및 메서드에는 반환 값이 없는 함수에 설명된 대로 암시적 반환 유형이 Void입니다.
  • 이것은 () 값 또는 빈 튜플을 반환함을 의미합니다.
  • 이 메서드는 반환 유형을 지정하지 않습니다.
  • 그러나 반환 유형이 없는 함수 및 메서드에는 반환 값이 없는 함수에 설명된 대로 암시적 반환 유형이 Void입니다.
  • 이것은 () 값 또는 빈 튜플을 반환함을 의미합니다.
  • 옵셔널 체이닝을 사용하여 옵셔널 값에서 이 메서드를 호출하면 반환 값이 옵셔널 체이닝을 통해 호출될 때 항상 옵셔널 유형이기 때문에 메서드의 반환 유형은 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."
  • 옵셔널 체이닝을 통해 프로퍼티을 설정하려고 하는 경우에도 마찬가지입니다.
  • 옵셔널 체이닝을 통한 프로퍼티 액세스의 위 예제는 거주 프로퍼티이 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

  • 옵셔널 체이닝을 사용하여 옵셔널 값의 하위 첨자에서 값을 검색 및 설정하고 해당 하위 첨자 호출이 성공적인지 여부를 확인할 수 있습니다.

    옵셔널 체이닝을 통해 옵셔널 값의 서브 스크립트에 액세스할 때 물음표는 서브 스크립트의 대괄호 앞이 아니라 뒤에 둡니다.
    옵셔널 체이닝 물음표는 항상 표현식의 옵셔널 부분 바로 뒤에 옵니다.

  • 아래 예제는 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")
  • 이 서브 스크립트 설정 시도도 현재 거주가 0이기 때문에 실패합니다.

  • 실제 Residence 인스턴스를 생성하여 john.residence에 할당하고 rooms 배열에 하나 이상의 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."

📍 Accessing Subscripts of Optional Type

  • 서브 스크립트가 옵셔널 유형의 값을 반환하는 경우(예: 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]
  • 위의 예는 String 키를 Int 값의 배열에 매핑하는 두 개의 키-값 쌍을 포함하는 testScores라는 사전을 정의합니다.
  • 이 예제에서는 옵셔널 체이닝을 사용하여 "Dave" 배열의 첫 번째 항목을 91로 설정합니다.
  • "Bev" 배열의 첫 번째 항목을 1씩 증가시키려면; "Brian" 키에 대한 배열의 첫 번째 항목을 설정하려고 합니다
  • testScores 사전에 "Dave" 및 "Bev"에 대한 키가 포함되어 있기 때문에 처음 두 호출은 성공합니다.
  • testScores 사전에 "Brian"에 대한 키가 포함되어 있지 않기 때문에 세 번째 호출은 실패합니다.

📌 Linking Multiple Levels of Chaining

  • 여러 수준의 옵셔널 체이닝을 함께 연결하여 모델 내에서 더 깊은 프로퍼티, 메서드 및 첨자를 드릴다운할 수 있습니다.

  • 그러나 여러 수준의 옵셔널 체이닝은 반환된 값에 더 많은 수준의 선택성을 추가하지 않습니다.

  • 요약:

    • 검색하려는 유형이 선택 사항이 아닌 경우 옵셔널 체이닝로 인해 선택 사항이 됩니다.
    • 검색하려는 유형이 이미 선택 사항인 경우 연결로 인해 더 이상 선택 사항이 되지 않습니다.
  • 그러므로

    • 옵셔널 체이닝을 통해 Int 값을 검색하려고 하면 Int? 사용되는 연결 수준에 관계없이 항상 반환됩니다.
    • 마찬가지로 Int? 옵셔널 체이닝을 통한 값, Int? 사용되는 연결 수준에 관계없이 항상 반환됩니다.
  • 아래 예시는 john의 거주지 프로퍼티의 address 프로퍼티의 street 프로퍼티에 접근을 시도합니다.

  • 여기에는 두 가지 수준의 옵셔널 체이닝이 사용되어 거주 및 주소 프로퍼티을 통해 연결되며 둘 다 선택 유형입니다.

    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의 값에는 현재 유효한 레지던스 인스턴스가 포함되어 있습니다.

  • 그러나 john.residence.address의 값은 현재 nil입니다. 이 때문에 john.residence?.address?.street에 대한 호출이 실패합니다.

  • 위의 예에서는 거리 프로퍼티의 값을 검색하려고 합니다. 이 프로퍼티의 유형은 String?입니다.

  • 따라서 john.residence?.address?.street의 반환 값은 프로퍼티의 기본 옵셔널 유형에 추가하여 두 가지 수준의 옵셔널 체이닝이 적용되더라도 String?이기도 합니다.

  • 실제 주소 인스턴스를 john.residence.address의 값으로 설정하고 주소의 거리 프로퍼티에 대한 실제 값을 설정하면 다단계 옵셔널 체이닝을 통해 거리 프로퍼티 값에 액세스할 수 있습니다.

    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

  • 앞의 예는 옵셔널 체이닝을 통해 옵셔널 유형의 프로퍼티 값을 검색하는 방법을 보여줍니다.
  • 옵셔널 체이닝을 사용하여 옵셔널 유형의 값을 반환하는 메서드를 호출하고 필요한 경우 해당 메서드의 반환 값을 연결할 수도 있습니다.
  • 아래 예제는 옵셔널 체이닝을 통해 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 doesn't begin with \"The\".")
        }
    }
    // Prints "John's building identifier begins with "The"."

    위의 예에서는 연결하는 옵셔널 값이 buildingIdentifier() 메서드 자체가 아니라 buildingIdentifier() 메서드의 반환 값이기 때문에 괄호 뒤에 옵셔널 체이닝 물음표를 배치합니다.

profile
I Am Groot

0개의 댓글