나의 기록, 현진록

[Swift] 14.1 옵셔널 체이닝 본문

Programming/Swift

[Swift] 14.1 옵셔널 체이닝

guswlsdk 2021. 9. 13. 10:28
반응형

스위프트 프로그래밍 3판 - 야곰 지음

 

 

14.1 옵셔널 체이닝

옵셔널 체이닝은 옵셔널에 속해 있는 nil일지도 모르는 프로퍼티, 메서드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정입니다. 옵셔널에 값이 있다면 프로퍼티, 메서드, 서브스크립트 등을 호출할 수 있고, 옵셔널이 nil이라면 프로퍼티, 메서드, 서브스크립트 등은 nil을 반환합니다. 즉, 옵셔널을 반복 사용하여 옵셔널이 자전거 체인처럼 서로 꼬리를 물고 있는 모양이기 때문에 옵셔널 체이닝이라고 부릅니다. 자전거 체인에서 한 칸이라도 없거나 고장 나면 체인 전체가 동작하지 않듯이 중첩된 옵셔널 중 하나라도 값이 존재하지 않는다면 결과적으로 nil을 반환합니다.

 

옵셔널 체이닝은 프로퍼티나 메서드 또는 서브스크립트를 호출하고 싶은 옵셔널 변수나 상수 뒤에 물음표(?)를 붙여 표현합니다. 옵셔널이 nil이 아니라면 정상적으로 호출될 것이고, nil이라면 결괏값으로 nil을 반환할 것입니다. 결과적으로 nil이 반환될 가능성이 있으므로 옵셔널 체이닝의 반환된 값은 항상 옵셔널입니다. 

 

 

더보기

느낌표(!)

물음표(?) 대신 느낌표(!)를 사용할 수도 있는데 이는 옵셔널에서 값을 강제 추출하는 효과가 있습니다. 물음표를 사용하는 것과 가장 큰 차이점은 값을 강제 추출하기 때문에 옵셔널에 값이 없다면 런타임 오류가 발생한다는 점입니다. 또 다른 점은 옵셔널에서 값을 강제 추출해 반환하기 때문에 반호나 값이 옵셔널이 아니라는 점입니다. 하지만 정말 100% nil이 아니라는 확신을 하더라도 사용을 지양하는 편이 좋습니다.

 

다음은 옵셔널 체이닝을 알아보기 위한 기본 클래스를 설계한 것이다.

 

class Room{                 //호실
    var number: Int         //호실번호
    
    init(number: Int){
        self.number = number
    }
}

class Building{             //건물
    var name: String        //건물 이름
    var room: Room?         //건물 정보

    init(name: String){
        self.name = name
    }
}


struct Address{             //주소
    var province: String    //광역시/도
    var city: String        //시/군/구
    var street: String      //도로명
    var building: Building? //건물
    var detailAddress: String? //건물 외 상세 주소
}

class Person{               //사람
    var name: String        //이름
    var address: Address?   //주소

    init(name: String){
        self.name = name
    }
}

 

 

hyunjin이 사는 호실 번호를 알고 싶습니다.

 

let hyunjin: Person = Person(name: "hyunjin")

let hyunjinRoomviaOptionalChaining: Int? = yagom.address?.building?.room?.number // nil

let hyunjinRoomviaOptionalUnwraping: Int = yagom.address!.building!.room!.number // 오류

hyunjin에는 아직 주소, 건물, 호실 정보가 없습니다. hyunjinnRoomviaOptionalChaining 상수에 호실 번호를 할당하려고 옵셔널 체이닝을 사용하면 hyunjin의 address 프로퍼티가 nil이므로 옵셔널 체이닝 도중 nil이 반환됩니다. 그러나 hyunjinRoomviaOptionalUnwraping 상수에 호출 번호를 할당할 때는 강제 추출을 시도했기 때문에 nil인 address 프로퍼티에 접근하려 할 때 런타임 오류가 발생합니다. 

 

 

옵셔널 체이닝의 동작 흐름은 옵셔널 체이닝 과정에서 하나라도 nil일 경우 도중에 nil을 반환하는 흐름을 가집니다.

 

옵셔널 바인딩을 사용하야 호실 정보를 가져오는 코드입니다.

let hyunjin: Person = Person(name: "hyunjin")

var roomNumber: Int? = nil

if let hyunjinAddress: Address = hyunjin.address{
    if let hyunjinBuilding: Building = hyunjinAddress.building{
        if let hyunjin: Room: hyunjinBuilding.room{
            roomNumber = hyunjin.number
        }
    }
}

if let number: Int = roomNumber {
    print(number)
}else{
    print("Can not find room number")
}

 

옵셔널 바인딩을 사용한 코드를 옵셔널 체이닝으로 표현하면 훨씬 간단해집니다.

 

let hyunjin: Person = Person(name: "hyunjin")

if let roomNumber: Int = hyunjin.address?.building?.room?.number{
    print(roomNumber)
}else{
    print("Can not find room number")
}

그러나 hyunjin.address의 값이 없기 때문에 nil을 반환합니다.

 

hyunjin.address?.building?.room?.number = 301
print(hyunjin.address?.building?.room?.number) //nil

옵셔널 체이닝으로 값을 할당해줄 수 있습니다. 그러나 hyunjin.address의 값과 그 하위값 모두 없기 때문에 옵셔널 체이닝 도중 중지 되어 어떠한 값도 할당해줄 수 없습니다.

 

 

 옵셔널 체이닝으로 값을 할당해보겠다.

 

hyunjin.address = Address(province: "경기도", city: "수원시 장안구", street: "창훈로", building: nil, detailAddress: nil)
hyunjin.address?.building = Building(name: "곰굴")
hyunjin.address?.building?.room = Room(number: 0)
hyunjin.address?.building?.room?.number = 301

 

메서드 호출의 경우에도 똑같다. 만약 메서드 반환 타입이 옵셔널이라면 또한 옵셔널 체이닝 사용이 가능하다.

 

 

우리가 서브스크립트를 가장 많이 사용하는 곳은 Array와 Dictionary입니다. 옵셔널의 서브스크립트를 사용하고자 할 때는 대괄호([])보다 앞에 물음표(?)를 표기해주어야 합니다. 이는 서브스크립트 외에도 언제나 옵셔널 체이닝을 사용할 때의 규칙입니다.

 

let optionalArray: [Int]? = [1,2,3]

optionalArray?[1] //2

var optionalDictionary: [String: [Int]]? = [String: [Int]]()
optionalDictionary?["numberArray"] = optionalArray
optionalDictionary?["numberArray"]?[2] //3

 

 

 

반응형