Optional Chaining (옵셔널 체이닝)
→ 옵셔널을 연쇄적으로 사용하는 것
옵셔널 체이닝은 옵셔널에 속해 있는 nil 일지도 모르는 프로퍼티, 메서드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정이다.
옵셔널이 nil 이라면 프로퍼티, 메서드, 서브스크립트 등은 nil로 반환한다.
즉, 옵셔널을 반복 사용하여 옵셔널이 자전거 체인처럼 서로 꼬리를 물고 있는 모양이기 때문에 옵셔널 체이닝이라고 부른다.
중첩된 옵셔널 중 하나라도 값이 존재하지 않는다면 결과적으로 nil 이 반환된다.
옵셔널 체이닝은 프로퍼티나 메서드 또는 서브스크립트를 호출하고 싶은 옵셔널 변수나 상수 뒤에 물음표(?) 를 붙여 표현한다.
옵셔널이 nil 이 아니라면 정상적으로 호출될 것이고, nil 이라면 결과값으로 nil 으로 반환할 것이다.
결과적으로 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
}
}
Swift
복사
let hj: Person = Person(name: "hj")
let hjRoomViaOptionalChaining: Int? = hj.address?.building?.room?.number
// nil
let hjRoomViaOptionalChaining: Int? = hj.address!.building!.room!.number
// 오류 발생
Swift
복사
옵셔널 바인딩 사용
let hj: Person = Person(name: "hj")
var roomNumber: Int? = nil
if let hjAddress: Address = hj.address {
if let hjBuilding: Building = hjAddress.building {
if let hjRoom: Room = hjBuilding.room {
roomNumber = hjRoom.number
}
}
}
if let number: Int = roomNumber {
print(number)
}
else {
print("Can not find room number")
}
Swift
복사
위의 내용을 옵셔널 바인딩 + 옵셔널 체이닝을 사용하여 구현
let hj: Person = Person(name: "hj")
if let roomNumber: Int = hj.address?.building?.room?.number {
print(roomNumber)
}
else {
print("Can not find room number")
}
Swift
복사
이처럼 옵셔널 체이닝을 통해 한 단계 뿐만 아니라 여러 단계로 복잡하게 중첩된 옵셔널 프로퍼티나 메서드 등에 매번 nil 체크를 하지 않아도 손쉽게 접근할 수 있다.
또한 옵셔널 체이닝을 통해 값을 받아오기만 하는 것이 아니라, 반대로 값을 할당해줄 수도 있다.
hj.address?.building?.room?.number = 505
print(hj.address?.building?.room?.number) // nil
// 아직 hj의 address 프로퍼티가 없으므로 nil
Swift
복사
옵셔널 체이닝을 통한 값 할당
hj.address = Address(province: "대구광역시", city: "수성구", street: "달구벌대로", building: nil, detailAddress: nil)
hj.address?.building = Building(name: "방구석")
hj.address?.building?.room = Room(number: 0)
hj.address?.building?.room?.number) = 505
print(hj.address?.building?.room?.number) // Optional(505)
Swift
복사
옵셔널 체인에 존재하는 프로퍼티를 실제로 할당해준 후 옵셔널 체이닝을 통해 값이 정상적으로 반환되는 것을 확인할 수 있다.
옵셔널 체이닝을 통해 메서드와 서브스크립트 호출도 가능하다.
서브스크립트는 인덱스를 통해 값을 넣고 빼올 수 있는 기능이다.
옵셔널 체이닝을 통한 메서드 호출
호출 방법은 프로퍼티 호출과 동일하다. 만약 메서드의 반환 타입이 옵셔널이라면 이 또한 옵셔널 체인에서 사용 가능하다.
// 주소
struct Address {
var province: String // 광역시/도
var city: String // 시/군/구
var street: String // 도로명
var building: Building? // 건물
var detailAddress: String? // 건물 외 상세주소
init(province: String, city: String, street: String) {
self.province = province
self.city = city
self.street = street
}
func fullAddress() -> String? {
var restAddress: String? = nil
if let buildingInfo: Building = self.building {
restAddress = buildingInfo.name
}
else if let detail = self.detailAddress {
restAddress = detail
}
if let rest: String = restAddress {
var fullAddress: String = self.province
fullAddress += " " + self.city
fullAddress += " " + self.street
fullAddress += " " + rest
return fullAddress
}
else {
return nil
}
}
func printAddress() {
if let address: String = self.fullAddress() {
print(address)
}
}
}
hj.address?.fullAddress()?.isEmpty() // false
hj.address?.printAddress() // 대구광역시 수성구 달구벌대로 방구석
Swift
복사
서브스크립트를 가장 많이 사용하는 곳은 Array와 Dictionary다.
옵셔널의 서브스크립트를 사용하고자 할 때는 대괄호([])보다 앞에 물음표(?) 를 표기해주어야 한다.
이는 서브스크립트 외에도 언제나 옵셔널 체이닝을 사용할 때의 규칙이다.
let optionalArray: [Int]? = [1, 2, 3]
optionalArray?[1] // 2
var optionalDictionary: [String: [Int]]? = [String: [Int]]()
optioanlDictionary?["numberArray"] = optionalArray
optioanlDictionary?["numberArray"]?[2] // 3
Swift
복사
옵셔널 체이닝의 특징
옵셔널 체이닝의 결과값의 타입은 마지막 표현식의 옵셔널 타입이다.
왜 반환값이 옵셔널이 붙는 걸까?
→ 만약 email에 닿기전에 sodeul이 nil이면 그냥 nil을 뱉어버리기 때문이다.
→ 이러한 이유로 옵셔널 체이닝의 반환값은 무조건 옵셔널 타입!
옵셔널 체이닝의 마지막 표현식은 옵셔널이더라도 ?를 생략한다.
struct Contacts {
var email: String?
var address: [String: String]
}
Swift
복사
이렇게 email의 변수를 옵셔널 String으로 바꿔주었다.
> 그럼 옵셔널이니까 접근할 때 옵셔널 체이닝에 의해 ?를 써줘야 하나??
let email = hj?.contacts.email? // '?' must be followed by a call, member lookup,
Swift
복사
→ 에러가 난다.
옵셔널 체이닝의 마지막 표현식은 옵셔널이든 아니든 ? 를 생략한다!