옵셔널 체이닝이란?
nil
일 수도 있는 프로퍼티나, 메소드, 서브스크립트에 질의(query)하는 과정
값이 있다면 그 값을 반환, 값이 nil
이면 nil
을 반환, 연결된 질의에서 하나라도 nil
이면 전체 결과도 nil
// 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가 옵셔널이기 때문에 최종 값은 옵셔널 값이 된다.
옵셔널 체이닝 여러 레벨로 사용 - 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."
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