[Swift]옵셔널 데이터타입 개념 및 예제 Value of optional type ‘XXX?’ must be unwrapped to a…
스위프트 옵셔널 데이터 타입은 대부분의 다른 프로그래밍 언어에 없는 새로운 개념이다. 옵셔널 타입의 목적은 변수 또는 상수에 값이 할당되지 않은 상황을 처리하기 위해 안전하고 일관된 접근 방식을 제공하는 것이다.변수를 선언할 때, 데이터 타입 선언 다음에 ‘?’ 문자를 두어 옵셔널이 되게 한다. 다음은 index라는 이름의 Int 타입의 변수를 옵셔널로 선언하는 코드다.
var index: Int?
이제 index 변수는 정숫값이 할당되거나 아무런 값도 할당되지 않을 수 있다. 내부적으로 컴파일러와 런타임의 관점에서 볼 때 어떤 값도 할당되지 않은 옵셔널은 실제로 nil의 값을 갖는다.
옵셔널은 할당된 값이 있는지를 식별하기 위한 테스트를 다음과 같이 쉽게 할 수 있다.
var index: Int?
if index != nil {
// index 변수는 값이 할당되어 있다
} else {
// index 변수는 값이 할당되어 있지 않다
}
만약 옵셔널에 값이 할당되었다면 해당 값이 옵셔널 내에서 ‘래핑되었다(wrapped)’고 말한다. 옵셔널 안에 래핑된 값을 사용할 때는 강제 언래핑(forced unwrapping)이라는 개념을 이용하게 된다. 간략하게 말해, 래핑된 값은 옵셔널 데이터 타입에서 옵셔널 이름 뒤에 느낌표(!)를 두어 추출되게 한다.언래핑의 개념을 좀 더 자세히 살펴보기 위해서 다음의 코드를 살펴보자.
var index: Int?
index = 3
var treeArray = ["Oak", "Pine", "Yew", "Birch"]
if index != nil {
print(treeArray[index!])
} else {
print("index does not contain a value")
}
간단하게 이 코드는 나무 종류 이름을 나타내는 문자열 배열의 인덱스를 담는 옵셔널 변수를 사용한다.
만약 index 옵셔널 변수에 값이 할당되면 배열의 해당 위치에 있는 나무 이름이 콘솔에 출력된다. index 변수가 옵셔널 타입이기 때문에 변수명 뒤에 느낌표를 두어 값이 언래핑된다.
print(treeArray[index!])
반대로, 앞의 코드에서 느낌표를 빼서 index 변수가 언래핑되지 않는다면 컴파일러는 다음과 같은 에러를 낼 것이다.
Value of optional type ‘Int?’ must be unwrapped to a value of type ‘Int’
강제 언래핑 대신, 옵셔널로 할당된 값은 옵셔널 바인딩(optional binding)을 이용하여 임시 변수나 상수에 할당할 수 있으며, 구문은 다음과 같다.
if let constantname = optionalName {
}
if var variablename = optionalName {
}
앞의 코드는 두 가지 작업을 수행한다. 첫 번째는 지정된 옵셔널이 값을 가지고 있는지를 확인하는 작업이다. 두 번째는 옵셔널 변수가 값을 가지고 있는 경우에 선언된 상수 또는 변수에 그 값을 할당하고 코드가 실행된다.
따라서 앞에서의 강제 언래핑 예제는 다음과 같이 옵셔널 바인딩을 사용하는 방법으로 수정할 수 있다.
var index: Int?
index = 3
var treeArray = ["Oak", "Pine", "Yew", "Birch"]
if let myValue = index {
print(treeArray[myValue])
} else {
print("index does not contain a value")
}
여기서는 index 변수에 할당된 값이 언래핑되어 myValue라는 임시 상수에 할당되어 배열에 대한 인덱스로 사용된다. myValue 상수를 임시로 설명한 부분에 주목하자.
왜냐하면 if 구문 안에서만 유효한 상수이기 때문이다. if 구문 실행이 끝나면 이 상수는 더이상 존재하지 않게 된다. 이러한 이유로 옵셔널로 할당된 동일한 이름을 사용해도 충돌이 발생하지 않는다. 다음의 예제도 유효한 코드다.
if let index = index {
print(treeArray[index])
} else {
}
다음의 구문처럼 옵셔널 바인딩은 여러 개의 옵셔널을 언래핑하고 조건문을 포함하는 데 사용될 수도 있다.
if let 상수명1 = 옵셔널 이름1, let 상수명2 = 옵셔널 이름2, let 상수명3 = ..., <조건식> {
}
예를 들어, 다음은 한 줄의 코드 내에서 두 개의 옵셔널을 언래핑하기 위하여 옵셔널 바인딩을 사용하는 코드다.
var pet1: String?
var pet2: String?
pet1 = "cat"
pet2 = "dog"
if let firstPet = pet1, let secondPet = pet2 {
print(firstPet)
print(secondPet)
} else {
print("insufficient pets")
}
반면, 다음의 코드는 조건문을 사용하는 예제다.
if let firstPet = pet1, let secondPet = pet2, petCount > 1 {
print(firstPet)
print(secondPet)
} else {
print("insufficient pets")
}
앞의 예제에서 petCount에 할당된 값이 1보다 크지 않다면 옵셔널 바인딩이 수행되지 않을 것이다.또한, 강제적으로 언래핑되도록 옵셔널을 선언할 수도 있다. 이런 방식으로 옵셔널을 선언하면 강제 언래핑이나 옵셔널 바인딩을 하지 않아도 값에 접근할 수 있다. 옵셔널을 선언할 때 물음표(?) 대신에 느낌표(!)를 사용하여 강제적으로 언래핑되도록 하는 것이다.
var index: Int! // 이제 옵셔널은 강제적으로 언래핑된다
index = 3
var treeArray = ["Oak", "Pine", "Yew", "Birch"]
if index != nil {
print(treeArray[index])
} else {
print("index does not contain a value")
}
이제 index 옵셔널 변수는 강제적으로 언래핑되도록 선언되어, 앞의 코드(print 함수 호출)에서 배열의 인덱스로 사용될 때 값을 언래핑할 필요가 없게 된다.
스위프트의 옵셔널에 대해 마지막으로 살펴봐야 할 부분은 할당된 값이 없거나 nil을 할당할 수 있는 것은 옵셔널 타입뿐이라는 점이다.
즉, 스위프트에서 옵셔널이 아닌 변수 또는 상수에는 nil을 할당할 수 없다. 예를 들어, 다음의 코드는 모두 컴파일 에러가 날 것이다.
그 이유는 옵셔널로 선언된 변수가 아니기 때문이다.
var myInt = nil // 유효하지 않은 코드
var myString: String = nil // 유효하지 않은 코드
let myConstant = nil // 유효하지 않은 코드
-알라딘 eBook <핵심만 골라 배우는 SwiftUI 기반의 iOS 프로그래밍> (닐 스미스 지음, 황반석 옮김) 중에서