값이 있을 수도, 없을 수도 있는 변수: 옵셔널(Optional)
이번 시간에는 **옵셔널(Optional)**에 대해 배워보겠습니다.
지금까지 우리는 변수나 상수를 선언할 때 반드시 어떤 값을 가져야 한다고 배워왔습니다. 두 가지 예를 보여드릴게요.
let name = "Paul"
var age = 5`
문자열 값을 가진 상수 name
과 정수 값 5를 가진 변수 age
가 있습니다. 여기까지는 새로운 내용이 없죠. 이 코드들이 유효한 이유는 둘 다 ‘값’을 가지고 있기 때문입니다.
사실 이것은 변수를 선언하는 여러 방법 중 하나일 뿐입니다. 이렇게 값이 반드시 있어야 하는 변수나 상수를 **’non-optional’**이라고 부릅니다. 그리고 그 반대편에는 우리가 만들 수 있는 또 다른 종류의 변수와 상수가 있는데, 바로 **’옵셔널(optional)’**입니다.
옵셔널은 왜 필요할까요?
옵셔널이 존재하는 이유는, 때로는 값이 없는 상태로 변수나 상수를 선언하고 싶을 때가 있기 때문입니다. 항상 특정 값을 가지고 시작하는 것이 불가능한 경우가 있거든요.
지금 당장은 이해하기 어려울 수 있지만, 앞으로 더 많은 예시를 보게 될 겁니다. 간단한 예로 소셜 네트워크 앱의 ‘사용자 프로필’을 생각해 봅시다.
앱에 가입할 때 ‘사용자 이름’은 거의 무조건 입력해야 합니다. 보장된 값이죠. 하지만 보장되지 않는 값들도 있습니다. 예를 들어 ‘자기소개(bio)’ 같은 항목은 프로필을 처음 만들 때는 비어있을 가능성이 높습니다. 그렇다고 소셜 네트워크가 임의의 자기소개를 만들어주지도 않죠.
그렇다면, “지금은 값이 없지만, 나중에는 문자열(String) 값이 들어올 수도 있는” 이 ‘자기소개’ 항목을 어떻게 표현해야 할까요? 바로 이럴 때 옵셔널이 등장합니다. 옵셔널은 “지금은 값이 없지만 나중에 값이 생길 수도 있는” 상황의 간극을 메워줍니다.
‘값이 없음’을 나타내는 nil
값이 없다는 상태를 표현하기 위해, Swift에는 **nil
**이라는 특별한 값이 있습니다. nil
은 말 그대로 **’값이 없음(no value)’**을 의미합니다.
- 다른 프로그래밍 언어를 경험해 보셨다면
nil
을null
과 비슷하다고 생각하시면 됩니다. - 특히 JavaScript를 아신다면,
null
과undefined
중에서null
에 해당합니다.
자, 그럼 아까 만들었던 age
변수를 옵셔널 버전으로 만들어 보겠습니다.
var optionalAge: Int? = nil
물론 age
라는 이름은 이미 사용했으니 ‘optionalAge’라는 다른 이름으로 만들었습니다.
여기서 두 변수의 차이점이 명확해집니다.
var age: Int = 5
: non-optional 입니다. 이 변수는 처음부터 끝까지 항상 정수 값을 가져야 합니다.var optionalAge: Int? = nil
: 옵셔널 입니다. 선언된 이 시점에는nil
, 즉 ‘값이 없는’ 상태를 가집니다.
물론 옵셔널이라고 해서 처음부터 꼭 nil
로 시작해야 하는 것은 아닙니다. 아래처럼 처음부터 값을 넣어줄 수도 있습니다.
Swift
var optionalAge: Int? = 5
이렇게 보면 일반 변수와 아주 비슷해 보이죠. 하지만 결정적인 차이가 있습니다. optionalAge
는 언제든지 nil
값을 가질 수 있다는 점입니다.
Swift
// 일반 Int 변수에는 nil을 할당할 수 없습니다.
age = nil // 에러! 'Int' 타입에는 nil을 할당할 수 없습니다.
// 옵셔널 Int? 변수에는 nil을 할당할 수 있습니다.
optionalAge = nil // 가능!
이것이 바로 옵셔널 변수와 상수가 가진 특별한 능력입니다. optionalAge
는 실제 정수 값을 가졌다가, nil
이 되었다가, 다시 다른 정수 값을 갖는 식으로 계속 상태가 바뀔 수 있습니다.
이런 기능은 앞서 말한 ‘자기소개’ 예시 외에도 다양하게 쓰입니다. 예를 들어, 서버에서 데이터를 가져오는 ‘네트워킹’ 작업을 할 때를 생각해 보세요. 데이터를 요청하면 앱에 도착하기까지 시간이 걸립니다. 그 시간 동안 데이터를 담을 변수는 ‘값이 없는’ 상태, 즉 nil
인 옵셔널로 표현해야 합니다. 데이터가 영원히 도착하지 않을 수도 있으니까요.
옵셔널 값을 안전하게 사용하기: 언래핑(Unwrapping)
이제 두 타입이 다르다는 것을 이해했으니, 왜 이 둘을 바로 더할 수 없는지 쉽게 이해할 수 있습니다.
let totalAge = age + optionalAge // 에러!
이 코드는 에러가 발생합니다. 왜일까요? 만약 optionalAge
가 nil
이라면, 5 + nil
이라는 연산의 결과는 무엇일까요? 말이 되지 않죠.
혹시 ‘nil
을 0으로 생각하면 되지 않나?’라고 생각할 수도 있습니다. 하지만 **0은 실제 ‘값’**이고, **nil
은 ‘값이 없음’**을 의미하는 상태입니다. 둘은 완전히 다릅니다. ‘값이 없음’을 5와 더할 수는 없는 노릇이죠.
optionalAge
가 5
라는 값을 가지고 있더라도 에러는 여전히 발생합니다. 왜냐하면 Swift는 optionalAge
가 옵셔널 타입이라는 것을 보는 순간, “이 안에는 값이 있을 수도 있지만, nil
일 수도 있어. 내가 멋대로 추측하지 않을 테니, 사용하는 네가 직접 확인해!”라고 요구하기 때문입니다. 이것은 실수를 방지하기 위한 아주 중요한 안전장치입니다.
옵셔널 변수 안의 값을 사용하려면, 값이 정말로 들어있는지 ‘확인하고 꺼내는’ 과정이 필요합니다. 이 과정을 **언래핑(Unwrapping)**이라고 합니다.
마치 크리스마스 선물을 상상해 보세요. 포장을 뜯기 전까지는 안에 무엇이 들었는지 알 수 없습니다. 착한 일을 했다면 멋진 선물이 있겠지만, 나쁜 일을 했다면 석탄 덩어리가 들어있을 수도 있죠. 여기서 **선물은 ‘실제 값’**이고, **석탄은 ‘nil
‘**인 셈입니다. 언래핑은 바로 이 선물 상자의 포장을 뜯어서 내용물을 확인하는 과정과 같습니다. nil
이 나왔다면 덧셈을 하면 안 되고, 실제 값이 나왔다면 그 값을 사용해서 덧셈을 해야겠죠.
위험한 방법: 강제 언래핑 (Force Unwrapping)
값을 꺼내는 방법에는 여러 가지가 있습니다. 먼저 위험하지만 간단한 방법부터 알아보죠. 바로 **강제 언래핑(Force Unwrapping)**입니다.
만약 제가 “optionalAge
에 100% 값이 들어있다고 확신해!”라고 장담할 수 있다면, Swift에게 “그냥 믿고 값을 꺼내 써!”라고 강제로 명령할 수 있습니다. 변수 이름 뒤에 **느낌표(!
)**를 붙이면 됩니다.
var optionalAge: Int? = 5
let totalAge = age + optionalAge! // optionalAge 뒤에 ! 추가
느낌표는 “나는 이 옵셔널에 값이 있음을 100% 확신하니, 값을 노출시켜 줘”라는 의미입니다. Swift는 그 말을 믿고 값을 꺼내 5와 더해줄 것이고, totalAge
는 10이 될 겁니다.
하지만, 만약 제 확신이 틀렸다면 어떻게 될까요?
var optionalAge: Int? = nil
let totalAge = age + optionalAge! // 치명적인 에러 발생! 앱이 강제 종료됩니다.
optionalAge
가 nil
인 상태에서 강제 언래핑을 시도하면, 존재하지 않는 값을 꺼내려다 **치명적인 에러(fatal error)**가 발생하고 앱은 그대로 멈춰버립니다.
그래서 강제 언래핑은 정말 명백하게 값이 있다는 것이 보장된 극소수의 상황을 제외하고는 사용을 지양하는 것이 좋습니다.
그렇다면 더 안전한 방법은 무엇일까요? 바로 if-let
이나 guard-let
같은 방법을 사용하는 것입니다. 이 안전한 언래핑 방법에 대해서는 곧이어 자세히 알아보겠습니다.