implicit optional unwrapping

암시적 언래핑은 옵셔널 타입 변수를 선언하면서 ? 대신 !를 선언한다. 특히 Objective-C API들은 객체를 리턴하는 경우가 많은데 이 때 객체들은 실질적으로 포인터이며 이는 항상 nil이 될 수 있으므로 옵셔널 타입이 된다. 즉, idAnyObject?가 되고, NSMutableArrayArray?가 된다는 의미이다. 그렇다고 해서 Objective-C API와 연동하는 코드 모든 곳에서 느낌표를 매번 붙여서 언래핑하는 것은 매우 불편하기 때문에 등장한 것이 아닌가 한다. (순수 Swift 코드에서는 나올 일이 별로 없을 것 같다.)

이 타입으로 정의된 변수는 값이 필요한 시점에 자동으로 언래핑되도록 한다.

var assumedString:String! = "Hello world!"
if (assumedString != nil) {
    println("\(assumedString)") // !를 쓰지 않음.
}

따라서 다음과 같이 동작함을 기억해 둘 필요가 있다.

  1. 타입끝에 !이 붙었다고 하더라도 이는 optional 타입이다.
  2. 이는 늘 nil이 아닌 값을 갖는다는 가정을 포함한다.
  3. 그렇다고 nil 값을 가질 수 없는 것은 아니다. Optional이기 때문에 nil이 될 수 있다.
  4. 값이 필요한 시점에 !을 쓰지 않아도 자동으로 언래핑된다.
  5. 단, 이 때는 ?를 썼을 때와 마찬가지로 nil이면 런타임 에러가 발생한다.
  6. 따라서 nil 검사는 항상 해줘야 한다.

그외 궁금증

SomeType!을 리턴하는 함수가 그냥 일반 옵셔널을 리턴해도 문제 없는지?

  1. 문제없다. !로 끝나도 옵셔널이다. 아래 사례 연구에서 보겠지만, 해당 값을 받아서 저장하는 변수의 타입이 중요하다. 옵셔널 타입으로 저장하면 옵셔널 타입으로 들어가고, 일반 타입으로 받으면 값이 그 때 언래핑되어 튀어나온다.
  2. 따라서 이런 함수를 호출한 후 결과값을 옵셔널이나 일반타입 변수로 받으면 된다.
  3. 만약 이 때 받는 측 변수도 SomeType!으로 정의되었다면, nil 검사만 하고, 일반 타입처럼 쓰면 되고,
  4. 일반타입 변수에 리턴 값을 대입했다면, 그냥 그대로 쓴다. (nil이면 컴파일 시점에 에러가 미리 날 것이다.)
  5. SomeType? 변수에 리턴값을 대입했다면? let s:Int? = 2를 생각하면 된다.

사례 연구

일반값을 리턴하는 함수

func someFunction() -> Int {
    let a:Int! = 2
    return a
}

let b = someFunction()

이 함수는 정수타입을 리턴한다. 함수 내에서는 정수옵셔널을 사용했지만, nil이 아닌 값을 리턴한다. 즉 리턴하는 시점에 자동으로 언래핑되면서 정수 타입이 리턴된다.

let c:Int? = someFunction()
// {Some 2}

이를 옵셔널 타입 값으로 대입하면 그 변수는 해당 값을 감싸서 옵셔널이 된다.

let d:Int! = someFunction()
// 2

여기서는 주의. d의 값 자체는 Some 2로 옵셔널이다. 하지만 nil이 아닌 이상, 이 변수를 어딘가에 사용하면 값이 필요할 때 자동으로 2로 언래핑될 것이다.

옵셔널 타입을 리턴하는 함수

func someFunction() -> Int? {
    let a:Int = 2
    return a
}

이 함수의 리턴타입은 옵셔널 정수타입이다. 하지만 함수의 본체에서는 그냥 정수값을 리턴한다. 옵셔널타입은 원 타입의 값을 내부에 갖는 형태이므로 이 함수는 리턴할 값을 자동으로 감싸서 리턴하게 된다.

let b:Int = someFunction()

이 구문은 에러다. 옵셔널에서 값을 꺼내려면 명시적으로 !를 붙여야 한다.

let b:Int = someFunction()!

이 맞는 표현이다.

let c:Int? = someFunction()

이는 당연히 맞는 구문이다. c는 사용될 때 c!로 표기해야 한다.

let d:Int! = someFunction()

이는 역시 맞는 표현이다. 옵셔널타입 변수가 옵셔널 타입값을 할당받았다. 단, 자동으로 언래핑되므로 d를 사용할 때는 !를 붙이지 않아도 된다는 차이가 있을 뿐이다.

암시적 옵셔널을 리턴하는 함수

func someFunction(a:Int) -> Int! {
    switch a % 3 {
        case 0:
            return nil
        case 2:
            return i * 3
        case _: // == default:
            let b:Int? = a + 1
            return b
    }
}

이 함수는 좀 복잡하면서도 흥미롭다. 물론 전체 케이스를 훑어보기 위한 용도로 이렇게 만든 것인데,

  1. 함수의 명시적인 리턴타입은 Int!이다.
  2. 인자값이 3의 배수이면 nil을 리턴한다. 함수의 리턴타입이 옵셔널이기 때문에 맞는 표현이다.
  3. 인자값이 3으로 나눠 2가 남으면 정수타입을 리턴한다. 하지만 함수의 리턴타입이 옵셔널이기 때문에 자동으로 래핑되어 옵셔널 타입이 리턴된다.
  4. 인자값이 3으로 나눠 1이 남으면 정수 옵셔널 타입을 리턴한다. 함수의 리턴타입이 옵셔널이기 때문에 특이사항이 없다.

다음을 보자.

let a:Int = someFunction(1)
let b:Int? = someFunction(1)
let c:Int! = someFunction(1)

이 세 구문은 모두 올바르다. someFunction의 결과값은 암시적으로 언래핑되는 정수타입이므로 a에는 2가, b와 c에는 Some 2가 들어가게 된다.

let d:Int = someFunction(2)
let e:Int? = someFunction(2)
let f:Int! = someFunction(2)

이 구문들 역시 똑같다. 함수 내부에서 어떤 값을 보내주더라도 리턴타입이 Int!임은 분명하고 이는 본질적으로 Int?와 차이가 없다. 단지 결과로 나온 값을 이후에 사용할 때 수동으로 언래핑해야한다는 차이만 있을 뿐이다. 즉 a == 2, b == Some 2, c == Some 2 이다.

let g:Int = someFunction(3) // <-- ERROR!! Int cannot be nil
let h:Int? = someFunction(3)
let i:Int! = someFunction(3)

여기서는 g가 문제가 된다. g는 옵셔널이 아니므로 nil을 대입할 수 없다. 따라서 g는 에러. h, i는 nil이 될 수 있다. 단, i는 필요한 시점에 자동으로 언래핑되므로, 이를 사용하기 이전에 반드시 nil 검사를 해야 한다. 그렇지 않으면 fatal error를 맞이한다.