[Swift] 옵셔널 바인딩(Optional Binding)과 암시적 추출 옵셔널(Implicitly Unwrapped Optionals)

 

옵셔널(Optional)
1. 값이 존재할 수도 있고 아예 존재하지 않을 수도 있다.
2. 값이 존재한다면 옵셔널을 언래핑(unwrap)하여 그 값에 접근할 수 있다.

 

Int의 0이나 String의 ""는 값이 존재하지 않음을 나타내는 것이 아니라 각각 0과 ""이라는 값을 가지고 있음을 의미한다. 값이 아예 존재하지 않을 때는 'nil'을 사용해 표현할 수 있다.

옵셔널은 타입 뒤에 물음표(?)를 붙여줌으로써 해당 객체가 옵셔널 객체임을 선언해줄 수 있다.

var optionalVal: String?
// optionalVal의 타입: Optional<String>
optionalVal = "hello, world!"
// Optional("hello, world!")
optionalVal = nil
// nilswift

 

옵셔널로 선언하지 않은 객체에는 nil을 할당할 수 없다.

var nonOptionalVal: String
// nonOptionalVal의 타입: String
nonOptionalVal = "hello, world!"
// hello, world!
nonOptionalVal = nil
// 컴파일 에러! 옵셔널이 아닌 변수에 nil을 할당할 수 없음
swift

 

만약 옵셔널 객체를 선언하고 특정 값을 할당해주지 않는다면, 객체는 자동으로 nil이 할당된다.
객체에 값이 존재하는지 존재하지 않는지 비교하기 위해 '== nil'(값이 존재하지 않음), '!= nil'(값이 존재함) 을 사용할 수 있다.

 

옵셔널 바인딩(Optional Binding)
옵셔널 객체가 값을 가지고 있는지 확인하고, 값이 존재할 때만 해당 값을 임시 상수 또는 변수로 사용할 수 있도록 값을 추출(unwrap)하는 것

if문, while문과 함께 사용할 수 있으며, 해당 옵셔널 객체의 값을 추출해 상수나 변수에 할당해준다.

다음 코드에서 optionalVal은 옵셔널 객체지만 그 값이 nil이 아니기 때문에 'hello, world!'가 출력된다.

var optionalVal: String? = "hello, world!"

if let val = optionalVal { // optionalVal값이 nil이 아닐 때 실행
    print(val) // hello, world!
} else { // optionalVal값이 nil일 때 실행
    print("This is nil")
}swift

 

위 코드에서는 optionalVal에 어떤 값도 할당하지 않았으므로 자동으로 nil이 할당되고 if문에서는 val에 값이 존재하지 않기 때문에 else로 내려가 'This is nil'가 출력된다.

var optionalVal: String?

if let val = optionalVal {
    print(val)
} else {
    print("This is nil")
}swift

 

let(상수) 대신 var(변수)를 사용해 할당받은 옵셔널 객체의 값을 바꿔줄 수도 있다.

var optionalVal: String? = "hello, world!"

if var val = optionalVal {
    val = "This is not nil"
    print(val) // This is not nil
} else {
    print("This is nil")
}swift

 

다수의 옵셔널 객체에서 값을 추출하고 싶다면 위 코드와 같이 콤마로 연결해줄 수 있다.

var optionalVal1: String? = "hello"
var optionalVal2: String? = "world"

if let val1 = optionalVal1, let val2 = optionalVal2 {
    print("\(val1) \(val2)") // hello world
} else {
    print("This is nil")
}swift

 

이때, 두 옵셔널 객체 중 단 하나라도 nil값인 객체가 존재한다면 false가 되기 때문에 else문으로 넘어간다.
옵셔널 객체에서 추출한 값은 if 블록 내에서만 존재한다. if 블록 밖에서는 해당 값을 사용할 수 없다.

 

암시적 추출 옵셔널(Implicitly Unwrapped Optionals)
옵셔널 바인딩이 비교적 부드러운 옵셔널 추출법이었다면, 암시적 추출 옵셔널은 보다 강압적인 옵셔널 추출법이다.
때때로 프로그램 구조상 옵셔널 객체에 무조건적으로 값이 존재하는 경우가 있을 수 있다. 이럴 때는 값의 존재 여부를 체크하는 것이 불필요하다. 그럴때 사용하는 것이 바로 암시적 추출 옵셔널.

 

옵셔널 객체로 만들고 싶은 객체의 타입 뒤에 물음표(?) 대신 느낌표(!)를 붙여 해당 객체가 암시적 추출 옵셔널이란 것을 선언할 수 있다.
옵셔널 객체를 선언해줄 때는 타입 뒤에 느낌표를 붙이고, 옵셔널 객체 내에 있는 값을 추출할 때는 상수나 변수 이름 뒤에 느낌표를 붙인다.

느낌표를 붙여서 옵셔널 객체의 값을 추출하는 방법을 '강제 언래핑(force-unwrap)'이라고 한다.

암시적 추출 옵셔널 객체는 그 값에 접근할 때마다 값의 존재 여부를 체크하지 않아도 되기 때문에 어떻게 보면 논옵셔널(non-optional) 객체와 유사하다고 할 수 있다.

 

암시적 추출 옵셔널은 해당 객체가 값을 반드시 가지고 있다는 가정 하에 사용하는 것이므로 값이 실제로 실행하기 전까지는 컴파일러가 에러를 잡아낼 수 없다. 값이 nil일 경우에는 런타임 에러가 발생한다.

let optionalVal1: String? = "hello"
let optionalVal2: String! = nil

 let val: String = optionalVal1
// optionalVal1은 옵셔널 객체라서 위 코드는 컴파일 에러가 발생함
// 값을 추출하기 위해서는 옵셔널 바인딩이나 혹은 암시적으로 추출해야 함
let val1: String = optionalVal1!
// 암시적 추출을 통해 값을 할당
let val2: String = optionalVal2
// optionalVal2는 암시적 추출 옵셔널 객체라 옵셔널 바인딩이나 암시적 추출이 필요하지 않음
// 단, 현재 optionalVal2의 값이 nil이기 때문에 런타임 에러가 발생함swift

그러므로 반드시 해당 객체가 nil이 아니라는 확신이 있을 때만 암시적 추출 옵셔널을 사용해야 한다.

nil체크는 기본 옵셔널 객체와 동일한 방법으로 수행할 수 있다.

let optionalVal: String! = "world"

if optionalVal != nil { // 기본 옵셔널 객체와 동일한 방법으로 nil 체크 가능
    print(optionalVal!) // world
}

if let str = optionalVal { // 옵셔널 바인딩을 통한 nil 체크 가능
    print(str) // world
}swift

 

옵셔널 객체의 값을 추출하기 위해 'Nil 병합 연산자(Nil-Coalescing Operator)'도 사용할 수 있다.
nil 병합 연산자는 (a ?? b) 형태로, a에는 옵셔널 객체가, b에는 a가 nil일 때 nil 대신 반환해 줄 기본값을 입력한다.
a != nil ? a! : b 와 같은 의미의 축약형이다.

let optionalVal1: String? = "hello"
print(optionalVal1 ?? "This is nil")
// hello

let optionalVal2: String? = nil
print(optionalVal2 ?? "This is nil")
// This is nilswift