Swift - 16. 옵셔널 체이닝 Optional Chaining

지우개·2022년 4월 1일
0

Swift study

목록 보기
5/15
post-thumbnail

옵셔널 체이닝이란?
nil일 수도 있는 프로퍼티나, 메소드, 서브스크립트에 질의(query)하는 과정
값이 있다면 그 값을 반환, 값이 nil이면 nil을 반환, 연결된 질의에서 하나라도 nil이면 전체 결과도 nil


강제 언래핑 대체로써의 옵셔널 체이닝 Optional Chaining as an Alternative to Forced Unwrapping

  • 프로퍼티, 메소드, 서브스크립트에서 옵셔널 사용 가능
  • 옵셔널 값을 강제 언래핑 했을 경우 (값이 없으면) nil이 반환됨
  • 옵셔널 체이닝에 의해 호출되면 반환 값과 같은 타입에 옵셔널이 붙어 반환됨. (Int → Int?)
// Residence와 Person이라는 인스턴스 생성
class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()
// john은 residence 프로퍼티가 nil인 값 소유

let roomCount = john.residence!.numberOfRooms
// 언래핑 할 값이 없기 때문에 런타임 에러 발생

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



john.residence = Residence()
// john.residence가 nil이 아닌 Residence 인스턴스를 갖기 때문에 numberOfRooms 값에 접근했을 때 nil이 아닌 Int? 값인 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)."

Residence 프로퍼티들은 묶여있다. 그래서 위 코드에서 numberOfRooms 프로퍼티는 옵셔널이 아니지만, 이 값에 접근하기 위해 사용했던 인스턴스의 프로퍼티 residence가 옵셔널이기 때문에 최종 값은 옵셔널 값이 된다.


옵셔널 체이닝을 위한 모델 클래스 정의 Defining Model Classes for Optional Chaining

옵셔널 체이닝 여러 레벨로 사용 - multilevel optional chaining

다음은 Person, Residence 모델을 확장해 Room과 Address 클래스를 추가한 4가지 모델을 정의한 예제이다.

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("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
        }
    }
}

프로퍼티 접근

// 프로퍼티에 접근
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"
john.residence?.address = someAddress

func createAddress() -> Address {
    print("Function was called.")
  let someAddress = Address()
    someAddress.buildingNumber = "29"
    someAddress.street = "Acacia Road"    

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

메소드 호출

func printNumberOfRooms() {
    print("The number of rooms is \(numberOfRooms)")
}
// 리턴 값이 없는 경우 암시적으로 Void 값을 갖는데, 옵셔널 체이닝에서 호출되면 반환 값은 Void?이 됨

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

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

서브 스크립트 접근

[] 괄호 전에 '?'를 붙여서 사용한다.

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

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

체이닝의 다중 레벨 연결 Linking Multiple Levels of Chaining

  • 상위 레벨 값이 옵셔널인 경우 그 값은 옵셔널이 됨
  • 옵셔널 체이닝을 통해 값을 검색하거나 메소드를 호출하면 몇 단계를 거치는지와 상관없이 옵셔널을 반환함

체이닝에서 옵셔널 값을 반환하는 메소드 Chaining on Methods with Optional Return Values


면접 질문

1. Optional 이란 무엇인지 설명하시오.

Swift에서 변수에 객체가 할당되지 않은 상태를 nil이라고 부르는데, optional이란 nil이 될 수 있는 변수의 타입을 의미한다. 기존 타입에 '?'를 붙여 표현한다.
Swift에서는 변수 선언 시 nil값이 들어가는 것을 허용하지 않고 컴파일 에러를 내는데, 이와 같은 상황이 발생하지 않도록 Optional 처리를 한다. 런타임에 nil로 인한 문제를 컴파일 단계에서 예방할 수 있다는 장점이 있다.
주의할 점
만약 A가 optional이고 B가 non-optional이라고 했을 경우, A와 B가 연산을 시도하면 컴파일 에러가 발생한다. A가 nil값을 가질 가능성이 존재하기 때문이다. 따라서 optional 변수를 이용해서 작업 시 optional을 해체하는 unwrapping이나 binding 과정이 필요하다.

(+)
다른 언어 C언어의 경우 null 할당 안하면 에러
스위프트의 안정성을 나타내는 대표적인 기능 중 하나임
switch
if let vs guard let
optional -> generic / enum 으로 구현


참고
https://jusung.gitbook.io/the-swift-language-guide/language-guide/16-optional-chaining
https://velog.io/@hayeon/Optional-이란-무엇인지-설명하시오
https://dblog.tistory.com/16?category=905799

0개의 댓글