Optional을 모르고 Swift를 사용하는 것은 Swift의 절반을 모르고 사용하는 것과 같다고 한다.
(중요하다는 것만 알고, 개념을 명확하게 정리하지 않아서 개발할 때 애매한 부분은 매번 구글링하면서 찾곤 했는데, 제대로 정리 GO.)
1. Optional (옵셔널)
1) 개념
위의 공식 홈페이지에서도 알 수 있듯, Swift는 안정 중심 설계(Designed for Safety) 라는 특성을 가지고 있다.
또 다른 안전 기능은 기본적으로 Swift 객체가 결코 nil이 될 수 없게 하는 것입니다. 실제로 Swift 컴파일러는 컴파일 시 오류가 있는 nil 객체를 만들거나 사용할 수 없도록 합니다. 이렇게 하면 코드를 훨씬 깔끔하고 안전하게 작성할 수 있으며 앱에서 거대한 카테고리의 런타임 충돌을 방지할 수 있습니다.
하지만 nil이 유효하고 적절한 경우도 있습니다. 이러한 경우를 위해 Swift는 Optional이라는 혁신적인 기능을 제공합니다. Optional에는 nil이 포함될 수 있지만 Swift 구문은 ? 구문을 사용하여 nil을 안전하게 처리하도록 함으로써 컴파일러에 동작을 이해하고 안전하게 처리될 것임을 표시합니다.
즉, nil에 강제적으로 접근하려고 하면 런타임 에러가 발생할 가능성이 큰데, 애초에 nil을 할당할 수 없는 타입들을 기본으로 하고, nil을 할당할 수 있는 타입으로 만들어주는 Optional이란 기능을 제공한다.
(1) Optional의 특성
- Optional 타입은 결국 값이 있을 수도, 없을 수도 있는 타입이다.
- Optional과 Optional이 아닌 것은 분명하게 다른 타입으로 인식한다.
- Optional이 아닌 것에 nil을 할당할 수 없다. (컴파일 오류)
(2) Optional에 nil이 할당되었을 경우
Optional에 nil을 할당할 수는 있지만, 값이 없는(nil) Optional 변수/상수에 강제로 접근하게 되면 (런타임 오류)가 발생한다. 이 경우, OS에서 프로그램을 강제 종료시킬 확률이 매우 높다. (이 과정은 추후에 자세히 알아보자.) 따라서 Optional을 사용한다면 값이 없을 수도 있음(nil)을 인지하고 그에 맞는 처리를 해줄 필요가 있다.
(3) Optional은 어떻게 구성되었을까?
Optional의 Declaration과 실제 정의를 찾아봤다.
@frozen enum Optional<Wrapped>
Playground에서 Optional 정의 부분에 직접 들어가봤다. 실제로 enum으로 구현되어 있는 것을 확인 할 수 있다. nil일 때는 case none, nil이 아닌 값이 있는 경우는 case some(Wrapped)로 구분되는데, 값은 case some의 연관 값(Associated Value)
으로 Wrapped에 저장된다. (네이밍이 이래서 중요한 듯. Optional의 순수한 값을 얻기 위한 행위를 Unwarpping이라고 한다.)
2. Optional Unwrapping (옵셔널 추출)
Wrapping 되어 있는 옵셔널 값을 옵셔널이 아닌 값으로 추출하는 것을 Optional Unwrapping(옵셔널 추출)이라고 한다.
1) Forced Unwarpping (강제 추출)
옵셔널 값의 뒤에 느낌표(!)를 붙여 강제로 추출할 수 있는 방법이다.
가장 간단하게 추출할 수 있는 방법이지만, 가장 위험한 방법이기도 하다. 추출한 값이 nil일 수도 있기 때문이다. (런타임 오류 발생 가능성)
강제 추출 방법은 사용을 지양하는 것이 좋다.
물론, if 구문을 통해 추출한 값이 nil인지 여부를 판단하고 사용할 수 있다.
하지만 이것보다 더 세련되게 nil 여부를 검사해서 사용할 수 있는 방법이 있으니.. 그것이 바로 다음 나오는 Optional Binding (옵셔널 바인딩) 이다.
2) Optional Binding (옵셔널 바인딩)
위에서 사용한 if를 통해 nil 여부를 검사하는 건 NULL을 검사할 때 다른 언어에서도 보통 많이 사용되는 방식이다. Swift는, 좀 더 안전하고 세련된 방법인 Optional Binding을 살펴 보시라.
옵셔널 바인딩이란?
옵셔널에 값이 있다면
옵셔널에서 추출한 값을 일정 블록 안에서 사용할 수 있는 상수/변수로 할당해,
옵셔널이 아닌 형태로 사용할 수 있도록 하는 것.
if, while 구문 등과 결합하여 사용할 수 있다.
쉼표로 여러 개의 Optional을 검사해서 Binding할 수도 있다.
Optional Binding은 뒤에 나오는 Optional Chaining과 환상의 결합을 이룰 수 있다. 뒤에서 다시 한 번 살펴보시라.
3) Implicitly Unwarpping Optionals (암시적 추출 옵셔널)
nil을 할당해줄 수 있는 옵셔널이 아닌 변수나 상수를 만들기 위한 '암시적 추출 옵셔널'
원래는 Optional이 아니면 nil을 할당해줄 수가 없는데, Optional은 아니지만 (== 값 그대로 사용할 수 있지만, == Optional Unwrapping 작업을 안해줘도 되지만) nil을 할당해줄 수 있는 방법이기 때문에 사용에 주의해야 한다.
+ 2022.06.23 추가)
암시적 추출을 사용하는 대표적인 예시는 @IBOutlet이다.
@IBOutlet weak var examinationImageView: UIImageView!
@IBOutlet weak var examinationLabel: UILabel!
암시적 추출의 필요성을 잘 모르겠어서 살펴보다가 좋은 자료를 발견했다.
처음 시작은 nil로 하지만, 사용할 시점에는 nil이 아니고 그 이후에도 nil이 되지 않는 대표적인 예시가 @IBOutlet.
만약 이를 옵셔널로 처리했다면, 사용할 때 옵셔널 언래핑을 지속적으로 해줘야 하는 번거로움이 있었을텐데, 이를 암시적 추출(Implicitly Unwarpping)로 해결할 수 있다.
And now you might be thinking “why would I want to take that risk?” The usual reason is that there are some things we all know will start life as being nil, but will be non-nil by the time we need them and won’t be nil again. For example, when you create outlets using Interface Builder it creates them all as implicitly unwrapped optionals because when your view controller is being created those outlets will all be nil, but shortly after they get set to real views and those won’t be destroyed until the whole view controller is destroyed.
출처: https://www.hackingwithswift.com/example-code/language/what-are-implicitly-unwrapped-optionals
Optional을 사용할 때는 Forced Unwrapping, Implicitly Unwrapping을 사용하기 보다는 Optional Binding, *nil 병합 연산자, Optional Chaining 등을 사용하는 것이 훨씬 안전하다.
*nil 병합 연산자
3. 심화 개념
1) Optional Chaining (옵셔널 체이닝)
옵셔널이 중첩되어 사용될 때 간결하게 사용할 수 있는 문법 (여러 프로퍼티, 메소드, 서브스크립트 등이 옵셔널 형태로 중첩되어 있을 때)
옵셔널 체이닝이 없다면 옵셔널은 정말 귀찮은 존재일 수 밖에 없다. (옵셔널인 경우 일일이 하나씩 체크해주고 사용해야 하니까)
체인 중에 하나라도 nil이 있으면 nil을 반환하고 모두 nil이 아닌 경우에만 Optional을 반환한다. (nil이 될 가능성이 있으므로 옵셔널을 반환하는 것임.)
Optional Chaining과 Optional Binding은 결합하여 사용할 수 있는데, Optional Chaining의 값이 Optional이기 때문이다.
Optional Chaining은 값을 받아오는 것뿐만 아니라 값을 할당해주는 것도 가능하다.
2) Early Exit (빠른 종료): guard 구문
guard 구문은 if 문처럼 Bool 타입의 값으로 동작하는 기능이다. 특정 조건에 부합하지 않다는 판단이 되면 빠르게 종료시키는 구문이다.
guard 구문은 항상 else 구문이 뒤에 따라와야 한다. 이 else 구문에는 꼭 자신보다 상의 코드 블록을 종료하는 코드가 들어가야 한다. 즉, return, break, continue, throw 등의 제어문 전환 명령어를 쓸 수 없는 상황이면 guard 구문은 사용할 수 없다.
다음 예시는 guard구문을 Optional Binding과 결합해서 사용한 예시이다.
Reference
- Apple Developer - Optional
- Swift.org - Documentation
- 책: 스위프트 프로그래밍: Swift 5 (야곰 지음)
'Development > Swift' 카테고리의 다른 글
[Swift] 필수 개념 (feat. Coding Test) (0) | 2022.03.16 |
---|---|
[Swift] Higher Order Function / 고차함수 (0) | 2022.02.25 |
[Swift] Closure (0) | 2022.02.17 |
[Swift] Extensions (0) | 2021.12.17 |
[Swift] Structures and Classes (0) | 2021.12.14 |