암시적 언래핑은 옵셔널 타입 변수를 선언하면서 ?
대신 !
를 선언한다. 특히 Objective-C API들은 객체를 리턴하는 경우가 많은데 이 때 객체들은 실질적으로 포인터이며 이는 항상 nil이 될 수 있으므로 옵셔널 타입이 된다. 즉, id
는 AnyObject?
가 되고, NSMutableArray
는 Array?
가 된다는 의미이다. 그렇다고 해서 Objective-C API와 연동하는 코드 모든 곳에서 느낌표를 매번 붙여서 언래핑하는 것은 매우 불편하기 때문에 등장한 것이 아닌가 한다. (순수 Swift 코드에서는 나올 일이 별로 없을 것 같다.)
이 타입으로 정의된 변수는 값이 필요한 시점에 자동으로 언래핑되도록 한다.
var assumedString:String! = "Hello world!"
if (assumedString != nil) {
println("\(assumedString)") // !를 쓰지 않음.
}
따라서 다음과 같이 동작함을 기억해 둘 필요가 있다.
- 타입끝에
!
이 붙었다고 하더라도 이는 optional 타입이다. - 이는 늘 nil이 아닌 값을 갖는다는 가정을 포함한다.
- 그렇다고 nil 값을 가질 수 없는 것은 아니다. Optional이기 때문에 nil이 될 수 있다.
- 값이 필요한 시점에
!
을 쓰지 않아도 자동으로 언래핑된다. - 단, 이 때는
?
를 썼을 때와 마찬가지로 nil이면 런타임 에러가 발생한다. - 따라서 nil 검사는 항상 해줘야 한다.
그외 궁금증
SomeType!
을 리턴하는 함수가 그냥 일반 옵셔널을 리턴해도 문제 없는지?
- 문제없다. !로 끝나도 옵셔널이다. 아래 사례 연구에서 보겠지만, 해당 값을 받아서 저장하는 변수의 타입이 중요하다. 옵셔널 타입으로 저장하면 옵셔널 타입으로 들어가고, 일반 타입으로 받으면 값이 그 때 언래핑되어 튀어나온다.
- 따라서 이런 함수를 호출한 후 결과값을 옵셔널이나 일반타입 변수로 받으면 된다.
- 만약 이 때 받는 측 변수도
SomeType!
으로 정의되었다면, nil 검사만 하고, 일반 타입처럼 쓰면 되고, - 일반타입 변수에 리턴 값을 대입했다면, 그냥 그대로 쓴다. (nil이면 컴파일 시점에 에러가 미리 날 것이다.)
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
}
}
이 함수는 좀 복잡하면서도 흥미롭다. 물론 전체 케이스를 훑어보기 위한 용도로 이렇게 만든 것인데,
- 함수의 명시적인 리턴타입은
Int!
이다. - 인자값이 3의 배수이면 nil을 리턴한다. 함수의 리턴타입이 옵셔널이기 때문에 맞는 표현이다.
- 인자값이 3으로 나눠 2가 남으면 정수타입을 리턴한다. 하지만 함수의 리턴타입이 옵셔널이기 때문에 자동으로 래핑되어 옵셔널 타입이 리턴된다.
- 인자값이 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를 맞이한다.