Swift - 옵셔널(Optionals)

옵셔널(Optionals) 이라는 개념은 스위프트(Swift) 만의 독특한 특징이라고 생각된다. 변수나 상수가 왜 nil을 가질 수 없는지 그리고 어떻게 하면 nil을 가질 수 있는지에 대한 질문에는 이 옵셔널이 해답을 준다.

nil? NULL?

nil이든 NULL 이든 null 이든 None 이든 대체로 비슷한 용도로 쓰이는 이름이 있다. 이 용어의 의미는 아래 처럼 설명이 가능 할 것 같다:
  • 존재하지 않음(Not Exists)
  • 정의되지 않음(Not Assigned, Not Defined)
  • 값이 없음(No Value)
  • 초기값(Initialized Value)
그런데 이 중 4번째 초기값이라는 표현은 개념상 틀린 말이다. C에서 NULL이 0으로 많이 사용되고 있어서 초기값 혹은 데이터가 존재하지 않음을 표시하는 수치로 많이 사용되고 있어서 초기값으로 이해하는 사람도 없지는 않겠지만, 어쨌든 초기값 용도가 아니다.

nil이든 NULL이든 그 의미는 '값이 존재하지 않음' 이 가장 가깝다.

그런데 존재하지 않다라는 표현을 어떻게 해야 하는가. 앞서 이야기 했지만 이런 존재하지 않는 특정 수치(대표적으로 0)를 별명(대표적으로 NULL)을 지어서 많이들 써 왔고 그 값이 0 혹은 특정 수치로 고정되기에 마지 데이터 처럼 취급되어 온 것이 바로 NULL이고 Objective-C의 nil도 비슷하게 이용되었다. 물론 이는 상황에 따라 모호함을 주는 요소이다.

스위프트는 타입을 명확하게 하기 위해 nil 이라는 이름을 특별한 용도로 사용하기 위해 디자인을 새롭게 했다. 거기서 탄생한 것이 바로 이 옵셔널(Optional) 개념이다. 스위프트에서 nil은 정말로 '데이터가 없음' 을 의미하는 것이다.

스위프트의 변수나 상수는 모두 기본적으로 nil 값을 가질 수 없다. 대신 이 글에서 설명할 옵셔널을 이용하면 nil 값을 가지게 만들 수 있다.

옵셔널로 변수를 생성하기

옵셔널은 두 가지 종류가 있다. 그냥 옵셔널(Optionals)과 암시적 옵셔널(원래 이름은 Implicitly Unwrapped Optionals)로 분류 할 수 있을 것 같다. 여기서는 그냥 옵셔널을 보자.

기본적으로 옵셔널을 정의할 때는 변수 타입 이름 뒤에 물음표(?)를 붙인다. 귀찮지만 실제 타입 이름인 Optional을 이용해 생성하는 것도 가능하다.
var name: String? = "My Name"
var yourName: String? = nil
var tmpName: String?                // nil
var tmpName2: Optional<String>      // nil
var tmpName3 = Optional<String>()   // nil
타입 이름 뒤에 물음표(?)를 붙이면 이 변수에는 옵셔널이 붙게 된다. 정확히 표현하자면 Optional 이라는 타입이 Int형 데이터를 가질 수 있게 만든 거지만... 두 번째 라인에서 볼 수 있듯이 기본적으로 대입이 불가능한 nil 이라는 값이 대입이 가능하다. 값을 생략하면 기본적으로 nil로 초기화가 된다.

이렇게 되면 해당 변수가 nil인지 아닌지 확인 할 수가 있게 된다.
var someValue: Int?

if someValue == nil {
    someValue = 10
}
위의 예에서 someValue가 nil 이라면 아직 값을 가지지 못한 단계라고 생각할 수 있게 된다.

옵셔널이 유용하게 사용되는 예제

String의 toInt() 라는 메소드는 문자열을 정수형으로 바꿀 때 이용한다. 그래서 마치 정수형(Int)을 리턴하는 것 처럼 보이지만, 그 명세를 들여다보면 Int? 타입을 리턴한다. 즉 옵셔널 정수형을 리턴한다는 의미다.

아래 예제를 보자
if "ABC".toInt() != nil {
    println("ABC is Integer???")
}
if "123".toInt() != nil {
    println("123 is Integer")
}
조금이라도 코딩을 해 봤다면 실행 결과가 두 번째 "123" 을 변환하는 것만 if 문을 통과한다는 것을 알 수 있다. 하지만 첫 번째 if 문에 사용된 toInt()는 옵셔널 nil 을 리턴한다. 즉 변환된 결과가 없다는 의미이다.

전통적인 C 언어 혹은 파생된 언어에서는 이런 경우의 처리가 모호했다. 만약 "ABC" 처럼 숫자로 변경이 불가능한 경우 어떤 숫자를 리턴해야 하나를 생각해보자. 0은 아니다. 음수도 아니다. 양수도 아니다. 그럼 무엇을 리턴해야 하는가? NULL? NULL은 애초에 어떤 값이 배정되어 있지 않던가? 0이면 어떡하지? ...

옵셔널은 이 부분에서 명확함을 더해준다. true와 false, 그리고 '없음' 을 명확하게 알려주는 것이다.

기타 옵셔널과 연관된 기능들

옵셔널 변수를 일반 변수로 바꾸기를 원한다면 변수 이름 뒤에 느낌표(!)를 붙이기만 하면 된다. 좀 더 자세한 정보는 옵셔널 엑세스에 관한 글을 보자.

if let 을 활용해 nil체크를 함과 동시에 옵셔널을 벗겨낼 수도 있다.
if let value = someOptionalValue {
    print(value)
}
이렇게 하면 if문 내부에서 value라는 이름의 값을 그대로 사용 할 수 있다. 물론 someOptionalValue가 nil 이라면 이 if문은 거짓으로 판단하고 블럭으로 진입하지 않는다.

'??' 오퍼레이터를 이용하면 nil인 경우의 대체값을 지정 할 수도 있다.
let intValue: Int = someOptionalIntValue ?? 0
이렇게 하면 someOptionalIntValue가 nil이면 intValue에는 0이란 값이, 그 외에는 someOptionalIntValue의 값이 들어가게 된다.

guard와 같은 특수 문법을 이용하면 미리 옵셔널을 판단해서 탈출시켜 버리는(Early Exist) 깔끔한 코드를 만들 수도 있다.

암시적 옵셔널 (Implicitly Unwrapped Optionals)

참고로 이 이름은 내가 멋대로 지은거다. 공식 명칭은 영어로 된 부분이다.

타입 이름 뒤에 물음표(?) 대신 느낌표(!)를 붙이거나 implicitlyUnwrapperOptional 타입으로 인스턴스를 생성하면 묵시적 혹은 암시적인 옵셔널을 정의할 수 있다. 동일하게 초기값을 생략하면 nil이 들어가게 된다.
var implicitOptionalValue: Int! = nil
var implicitOptionalValue2: Int!                               // nil
var implicitOptionalValue2: ImplicitlyUnwrappedOptional<Int>   // nil
암시적이니 묵시적이니 implicit니 말이 참 이해하기 어려운데, 이 방식의 옵셔널도 nil 대입 및 초기화 패턴을 제공하는게 목적이다.

이런 방식은 앞서 본 기본 옵셔널과 기능적으로는 별 차이가 없다. C나 Objective-C를 이해하고 있다면 포인터에 대해 알 것이다. 이 포인터는 주소(address)를 가리키기 위한 특수 변수이고 0(NULL 혹은 nil)이라는 주소는 사용 할 수 없기 때문에 NULL 혹은 nil일 경우는 존재하지 않는 혹은 잘못된 포인터로 가정하고 에러처리를 할 수 있다. 이런 방식을 Swift 식으로 단순하게 매핑한 것이다. 그냥 단순하게 nil 값이 지정만 가능하게 했다고 할 수 있을까?

즉,  완벽한 옵셔널이 아니라는 점에서 (특수한 사유가 없다면) 가급적이면 이것 보다는 기본형 옵셔널을 사용하는 것이 좋다. 주로 이 암시적인 옵셔널은 Objective-C나 C로 구현된 프레임워크와의 호환성 때문에 사용되는 것 같은데 시간이 흐르면 기본형 하나로 통합될 가능성이 높다고 생각된다.

실제 환경에서 테스트 해 보면 일반 옵셔널은 Some 뭐시기 라는 표현이 붙는데 암시적인 옵셔널로 만들면 이런 Some 뭐시기 라는 표현이 붙이 않는다. 분명히 둘은 차이가 있다.

간혹 실패할지도 모르는 생성자(Failable Initializer)에서 return nil을 하기 전에 모든 변수를 초기화 해야 하는 것을 건너뛰기 위해 멤버 프로퍼티를 이 암시적 옵셔널로 설정하는 트릭이 종종 쓰일 때도 있다. 필수는 아니니 자세히 언급하지는 않겠지만...

참고사항

앞서 NULL 값이 0이니 뭐니 말이 많았지만, 이건 C 같이 단순한 언어일 경우에 한한 말이다. 현대적인 언어는 값이 존재하지 않는다는 것을 숫자가 아닌 다른 형식의 이름으로 부여해 놓았다. 이를테면 Python의 None이라는 값이 그것이다.

하지만 실제로 C의 경우 NULL이 특정 수치를 가리키고, 대체로 NULL은 0이었다. 그래서 숫자를 리턴하는 경우 값이 없음을 NULL로 리턴하기 곤란한 경우가 많았고 그래서 NULL이라는 수치는 포인터(Pointer, 메모리 어드레스) 중 존재하지 않는 포인터를 지칭하는 용도로 많이 사용되었다. 숫자를 리턴해야 하는 경우는 구조체(struct) 등 별도의 포인터를 이용해 리턴하는 방식으로 바꾸는 등 NULL의 모호함을 회피하려 다양한 방법을 활용했다.

애초에 이 사항은 스위프트에서는 크게 신경 쓸 필요는 없다. 그저 옵셔널이 있고 그 용도를 알아두는 선으로 넘어가는게 좋다.

[관련글] Swift - 옵셔널(Optional) 엑세스​
[돌아가기] 스위프트(Swift) 가이드

댓글

익명님의 메시지…
많은 도움 되었습니다!
김덕후님의 메시지…
덕분에 좀 더 개념을 명확하게 잡고 접근을 할 수 있게 되었네요.
고맙습니다.

이 블로그의 인기 게시물

버전(Version)을 제대로 이해하기

소수점 제거 함수 삼총사 ceil(), floor(), round()