옵셔널 체이닝(Optional Chaining)은 현재 nil일 수도 있는
옵셔널 프로퍼티, 메서드, 서브스크립트를 참조하고 호출하는 과정임!!!
만약 옵셔널(Optional)이 값을 가지고 있다면 프로퍼티, 메서드, 서브스크립트는 성공적으로 호출됨!!
만약 하나라도 nil이라면 nil을 반환함!!!!
.(dot)을 통해 내부 프로퍼티나 메서드에 연속적으로 접근할 때 옵셔널(Optional) 값이 하나라도 껴 있으면 옵셔널 체이닝(Optional Chaining)!!!
예제를 한번 보자!!
struct Contacts {
var email: String
var address: [String:String]
}
struct Person {
var name: String
var contacts: Contacts
init(name: String, email: String, address: String) {
self.name = name
contacts = Contacts(email: email, address: ["home" : address])
}
}
var james: Person? = Person(name: "james", email: "james@naver.com", address: "korea")
옵셔널(Optional) Person 타입인 james 이란 변수를 선언 했음
이렇게 james.contacts.email 에 접근해 보면
james.contacts.email
// error: Optional Chaining.xcplaygroundpage:20:1: error: value of optional type 'Person?' must be unwrapped to refer to member 'contacts' of wrapped base type 'Person'
// james.contacts.email
에러가 뜸 !!! james는 옵셔널 타입(Optional Type)이기 때문!!

이렇게 변경하면 에러가 사라짐!!
이게 바로 옵셔널 체이닝(Optional Chaining) 임!!! 💡
옵셔널(Optional) 표현식의 멤버에 접근할 때, 표현식이 nil일 수 있으니? 를 써주는 것임
만약 nil 이면

nil 이 반환됨!!
만약 james, contacts, email 중 단 하나의 표현식이라도 nil 이라면 결과값은 nil 임
그리고
옵셔널 체이닝(Optional Chaining) 의 결과값의 결과값의 타입은 마지막 표현식의 옵셔널 타입(Optional Type)
let email = james?.contacts.email

email 타입에 옵셔널을 씌운 Optional <String>
그 이유는!!!

내가 원하는 email에 닿기 전에 james이가 nil 이면 그냥 nil 임
이런 가능성 때문에 옵셔널 체이닝(Optional Chaining) 의 반환값은 무조건 옵셔널 타입(Optional Type)임!!!
만약 jamesl이 nil 이라서 nil 이 반환되어도 반환값은 맨 마지막 표현식 타입일까????

어떤 조건이든 옵셔널 체이닝(Optional Chaining)의 결과값은 무족권 마지막 표현식의 옵셔널 타입임
그리고 옵셔널 체이닝(Optional Chaining)의 마지막 표현식은 옵셔널(Optional) 이더라도? 를 생략하는데
무슨 말이냐면!!!
struct Contacts {
var email: String?
var address: [String:String]
}
우선 구조체를 변경해 보자!!
email의 변수를 옵셔널(Optional) String으로 바꿔 줌!!
email이 옵셔널(Optional) 이니깐 접근할 때? 사용해야지 하고 사용하면

옵셔널 체이닝(Optional Chaining)의 마지막 표현식은 옵셔널(Optional)이든 아니든!!! 생략함!!
만약 email 변수의 속성에 접근한다면

? 를 사용하면 되고!! 에러 안남!!!
더 이상 email이 마지막 표현식이 아니기 때문임!!!!
함수가 포함된 옵셔널 체이닝(Optional Chaining)
struct Person {
var name: String
var contacts: Contacts
init(name: String, email: String, address: String) {
self.name = name
contacts = Contacts(email: email, address: ["home" : address])
}
func getContacts() -> Contacts? {
return contacts
}
}
Person 구조체에 getContacts() 함수를 만들었음!!
그리고 이 함수의 리턴값으로 얻은 contacts의 email 값에 접근하려 함
그럼 다음과 같이
let email = james?.getContacts()?.email
함수의 리턴 값이 옵셔널(Optional) 인 경우엔 함수를 호출하는 ( ) 뒤에 ? 를 붙여주면 됨
( ) 뒤에 ?가 붙는 경우는 함수의 리턴 값의 속성에 접근할 때
함수 호출 ( ) 앞에도 ? 가 붙을 수 있는데!!
그 경우는 바로 함수 자체가 옵셔널(Optional)인 경우
func getContacts() -> Contacts {
return contacts
}
이렇게 getContacts의 리턴 값을 Non-Optional Type으로 변경해 주면
리턴 값이 Non-Optional Type이고
let function = james?.getContacts
getContacts 함수를 담은 function이란 상수는 Optional Type인 경우
let email = function?().email
?( ) 요렇게 사용함!!!
그럼 ?( )? 이런 것도 사용될까????
당연!!
그럼 ?( )? 이건 언제 쓰느냐 ~~
이건 다음과 같이 함수 자체도 옵셔널(Optional)이고, 리턴 값도 옵셔널(Optional) 일 때 사용함!!
자!!
getContacts 함수의 리턴값을 Optional Type으로 다시 지정해 보자
func getContacts() -> Contacts? {
return contacts
}
getContacts의 리턴 값을 Optional Type으로 지정했음

앞에 예시와 똑같이 사용하면
이렇게 오류남!!!
왜냐??
리턴 타입이 Optional Type이며 getContacts 함수를 담은 function이란 상수가 Optional Type이기 때문

요렇게 사용하면 해결
?( ) -> function이란 함수가 먼저 옵셔널(Optional)인지 아닌지 체크해 주고
( )? -> function 함수가 옵셔널(Optional)이 아니라면 리턴값이 옵셔널(Optional)인지 아닌지 체크해 주는 것임!!
딕셔너리 옵셔널 체이닝
let dic: [String: String] = ["name" : "james"]
이런 딕셔너리가 있을 때
name이라는 키 값에 접근해서, 그 value 값을 count 해보자!!

어!! 근데 에러가 남!!
딕셔너리에서 key를 통해 value를 얻을 땐
해당 key가 없을 수도 있기 때문에 무조건 Optional Type이 return 됨
따라서 딕셔너리의 key 값으로 얻은 value 값의 속성에 접근할 때에는 [ ]? 를 사용함
근데 이것은 Framework 자체가 무조건 옵셔널 타입을 리턴하기 때문에
무조건 옵셔널 체이닝(Optional Chaining)을 해줘야 함!!!
dic["name"]?.count
이렇게 [ ]? 해줘야 함!!!
만약!! dic이 옵셔널(Optional)인 경우에는 ~~
let dic: [String: String]? = ["name" : "james"]
dic?["name"]?.count
이렇게 해줘야 함 !!!
dic이 nil인지 아닌지 체크하고 key "name"으로 얻은 value 값이 nil인지 아닌지 체크함
암시적 언래핑 옵셔널(Implicitly Unwrapped Optionals)
암시적 언래핑 옵셔널(Implicitly Unwrapped Optionals)
https://h2kangrok.tistory.com/37
[Swift] 옵셔널(Optional)[2] 강제 추출(Forced Unwrapping), 옵셔널 바인딩(Optional Binding)
저번 글에서 옵셔널(Optional)에 대해서 공부해 봄 저번글에 보면 var bandName: String? = "The 1975" print(bandName) // Optional("The 1975") 옵셔널 변수가 값을 가질 때 값만 출력되는 것임 아니라 Optional( ) 이라는
h2kangrok.tistory.com
🔼 위에서 알아본 강제추출처럼
암시적 언래핑도 강제 언래핑과 비슷하게 nil 인지 체크
다만 차이점은 옵셔널 타입(Optional Type)을 '선언'하는 시점에 타입 뒤에 ! 를 붙여준다는 점!!!
var bandName: String! = "The 1975"
print("좋아하는 밴드는 \(bandName)")
// 좋아하는 밴드는 Optional("The 1975")

이때 name 상수는 Optional <String>이 맞다. 하지만 Swift 컴파일러가 언래핑을 강제하지 않음!! !
실제로 nil 값을 가질 수 있는 옵셔널이지만, 옵셔널이 '아닌 것처럼' 쓸 수 있음!!!
var age: Int! = 10
print(age + 10)
// 20
암시적으로 언래핑한 옵셔널은 옵셔널 언래핑(Optional Unwrapping) 처리를 해주지 않아도 사용할 수 있음!!
암시적으로 언래핑한 옵셔널은 값에 접근할 때마다 옵셔널 값을 강제 언래핑을 하는 것과 같음!!
때문에 옵셔널 바인딩이나 nil 병합 연산을 하지 않아도 옵셔널이 벗겨진 상태로 사용이 가능함!!!
타입 뒤에 붙은 ! 가 '이 타입은 옵셔널이 맞는데 꼭 체크 안해도 돼.' 라고 Swift 컴파일러에게 말하는 표시인 셈
강제 언래핑에서 보았듯이, nil 이 담긴 옵셔널을 강제 언래핑하면 프로그램이 비정상적으로 종료됨.
그렇기 때문에 nil 이 담겨있는 암시적으로 언래핑한 옵셔널을 사용하면, nil 이 담긴 옵셔널(Optional)을 강제 언래핑하는 것과 마찬가지기 때문에 runtime에 프로그램이 비정상적으로 종료!!
비정상적으로 종료가 되는 걸 쓸 필요가 있을까???? 하고 하면 !!!
가끔 이런 옵셔널(Optional)이 필요할 수 밖에 없는 때가 있다고 함!!
어떤 값이 처음에는 nil 이지만, 우리가 그걸 다루려고 할 때는 무조건 nil 인 상황이 있음!!
만약 !!! 이럴 때는 매번 언래핑을 하는 것보다 암시적 언래핑을 한 타입으로 선언하는 게 낫다고 함 !!
안전성이 확실하다면, 편의성을 위해서 강제 nil 체크를 풀어줄 수 있다는 뜻임 !!
대표적인 사례가 바로 인터페이스 빌더의 아웃렛(IBOutlet) !!
@IBOutlet var imageView: UIImageView!
인터페이스 빌더에서 Outlet을 만들 때 실제 동작을 보자~~~~~~ 🔽
먼저, Vide controller 객체가 먼저 생성이 되고
View Controller를 생성할 시기에는 Outlet이 nil 임
하지만 View가 실제로 불러와지고 나면, Outlet에 값이 할당
View Controller가 없어질 때까지 Outlet의 값은 없어지지 않음!!!
즉, 우리가 코딩을 할 때엔 Outlet에는 값이 '있다'는 것이 100% 확실!
그래서 UIKit은 알아서 IBoutlet을 암시적 언래핑한 옵셔널로 만들어 놈!
애플 형님들이 '값 들어있는 거 확실하니까 걱정하지 말고 써' 하면서 ❗️를 붙여줬구나...
하고 이해하면 된다 ~~~~~~ 라고 함 !!!!!
참고 및 인용
'Swift' 카테고리의 다른 글
| [Swift] 클로저(Closures) (3) | 2024.05.05 |
|---|---|
| [Swift] 옵셔널(Optional)[4] nil 병합 연산자(??) (Nil-Coalescing Operation) (0) | 2024.03.28 |
| [Swift] 옵셔널(Optional)[2] 강제 추출(Forced Unwrapping), 옵셔널 바인딩(Optional Binding) (2) | 2024.03.26 |
| [Swift] 옵셔널(Optional) [1] (0) | 2024.03.26 |
| [Swift] Combine 알아보기[2] @Published (Publisher), Operator, Scheduler (4) | 2024.03.17 |