[Swift] Optional과 Optional Binding

 

Optional
변수에 값이 있을 수도 있고 없을 수도 있음을 암시

'데이터가 아예 존재하지 않음'을 나타내는 nil(다른 언어에서는 null)의 가능성을 명시적으로 표현한다.
nil의 가능성에 대해 문서화하여 전달하지 않아도 코드만 보고 알아챌 수 있기 때문에 효율적이다.
즉, 전달받은 값이 옵셔널 값이 아니라면 nil이 들어갈 수 없으므로 nil체크를 하지 않아도 안심하고 사용할 수 있다.
(흔히 java에서 발생하는 NullPointerException이 발생하지 않는다.)
Swift에서 강조하는 안전한 코딩이 가능하다!

옵셔널은 변수의 타입 뒤에 '?' 또는 '!'가 붙어 쉽게 파악할 수 있다.

 

- '?'가 붙은 (일반) 옵셔널

var num: Int? // nil
var num2: Int = nil // 컴파일 에러

이 경우 변수 num에 값을 할당해주지 않았으므로 nil로 초기화된다.
nil은 옵셔널이 아닌 변수에는 할당될 수 없다.

 

- Optional변수는 Wrapping되어있는 상태다.

var num: Int? = 100 // Optional(100)

옵셔널 변수는 안에 nil이 들어있는지 아닌지 모르기 때문에 기본적으로 wrap 되어있는 상태다. 따라서 변수 안에 값이 있다고 하더라도 곧바로 변수의 값을 출력하지 않고 Optional(값)의 형태로 Optional에 wrapping된 상태로 출력된다.

 

- (일반) 옵셔널 변수에는 nil을 할당할 수 있기 때문에 다른 일반 변수들과 함께 연산할 수 없다.

var num: Int? = 100
var num2: Int = 200
print(num + num2) // 컴파일 에러

 

- '!'가 붙은 암시적 추출 옵셔널

var num: Int! // nil

일반 옵셔널과 달리 타입 뒤에 '!'가 붙은 옵셔널 또한 기본 값은 nil로 초기화된다.
'암시적 추출'이라는 말처럼 강제로 옵셔널 안에서 값을 꺼낸다는 의미로, 옵셔널 안에 nil이 아닌 일반 값이 들어있을 거라고 추측한 뒤 해당 값을 강제로 꺼내는 것이다.

 

- 암시적 추출 옵셔널은 (조건부로) 다른 일반 변수들과 함께 연산 가능

var num: Int = 100
var num2: Int! = 200
print(num + num2) // 300
var num3: Int! = nil
print(num + num3) // 런타임 오류

암시적 추출 옵셔널 안에 nil이 아닌 일반 값이 할당되어있다면 다른 일반 변수들과 마찬가지로 평범하게 연산될 수 있다.
하지만 만약 암시적 추출 옵셔널 안에 nil이 할당되어있고, 이 값을 다른 일반 변수와 함께 연산하려고 한다면 런타임 에러가 발생할 수 있다.
컴파일 단계에서는 이 오류를 잡아낼 수 없으므로 우리는 이 값이 옵셔널이 아니라고 확신할 수 있는 안전한 방법이 필요하다.

 

옵셔널 바인딩(Optional Binding)
옵셔널을 추출하는 안전한 방법

그렇다면 옵셔널 안에 있는 값이 nil인지 아닌지 확인하고, nil이 아니라면 해당 값을 안전하고 편하게 사용할 수 있는 방법은 없을까? = 옵셔널 바인딩

 

- if let 옵셔널 바인딩

var optionalNum: Int? = 100

if let num = optionalNum {
    print(num) // 100
}

if let 구문을 통해 =의 오른편에 있는 옵셔널 변수의 값이 nil이 아닌 경우 변수의 값을 =의 왼편에 있는 변수에 할당해주는 방식
if let 구문은 옵셔널 변수의 값이 nil이 아닐 때만 실행되며, if 구문 밖에서는 해당 변수를 사용할 수 없다.

 

- if let 옵셔널 바인딩은 한번에 여러개도 가능

var optionalNum: Int? = 100
var optionalNum2: Int? = 200
var optionalNum3: Int? = nil

if let num = optionalNum, let num2 = optionalNum2 {
    print(num + num2) // 300
}

if let num = optionalNum, let num3 = optionalNum3 {
    print(num + num3) // 실행되지 않음
}

 

if let 옵셔널 바인딩은 한번에 여러개의 옵셔널 변수도 바인딩할 수 있다.
단, 그 중에서 하나라도 값이 nil인 경우에는 해당 if문이 실행되지 않는다.

 

- guard let 옵셔널 바인딩

var optionalNum: Int? = 100
var optionalNum2: Int? = 200
var optionalNum3: Int? = nil

guard let num = optionalNum, let num2 = optionalNum2 else {
    preconditionFailure()
}

print(num + num2) // 300

guard let num = optionalNum, let num3 = optionalNum3 else {
    preconditionFailure() // 런타임 에러
}

print(num + num3)

guard는 assert와 함께 애플리케이션 동작 도중 동적으로 생성되는 다양한 변수들을 확인하고 안전하게 처리할 수 있도록 도와주는 기능이다.
잘못된 값이 들어왔을 때 특정 실행 구문을 더이상 수행하지 않고 빠르게 종료한다.
guard는 항상 else와 함께 쓰여야 한다.
그중에서도 guard let 구문은 if let과 마찬가지로 안전한 옵셔널 바인딩이 가능하다.

또한, if let 구문과 달리 guard let 구문에서 만들어진 변수는 해당 블록 밖에서도 사용이 가능하다.

두번째 guard let 구문에서 num3가 nil이기 때문에 그 다음 코드는 실행되지 않고 preconditionFailure()을 통해 오류를 throw하며 애플리케이션이 동작을 멈춘다.