Optional의 존재는 nil 로부터 온다.
nil 의미
Swift 에서의 nil 은 Objective-C 에서의 nil 과 다르다.
- nil of Swift : 값이 없다 in 모든타입 (Reference or Value Type)
- nil of Objective-C : 값이 없다 in Reference Type
즉, Swift에선 어떤 타입이든 값이 없으면 nil 이다.
Optional Type
Optional Type 은 nil이 될 수도 있는 변수 (or 상수)를 의미하며 선언할 때 ‘?’ 를 사용한다. ‘?’ 이 없으면 Non-Optional Type 으로 nil이 될 수 없다.
// optional type. var optionalString : String? // nil 로 초기화 // non-optional type. var nonOptionalString : String // nil 이 될 수 없음. 초기화 필요
WWDC 2014 에서 Safe, Power, Modern 세가지를 Swift의 특징으로 꼽았다. Optional은 Safe에 속한다.
기존 nil 로 인한 앱 크래쉬 문제를 nil 전용 변수 타입을 따로 정의해서 compile 시점에서 막아줄께 하는 것이다. (실제로 이것이 얼마나 안전한지는 더 써봐야 알겠다.)
개발자는 변수 선언할 때마다 Optional Type으로 선언할 것인지 판단해야 한다. 꼭 필요한 경우에만 Optional Type 사용을 권장한다.
Optional은 enum 으로 정의된다.
enum Optional<T>{ case None case Some(T) }
! (forced-unwrapping operator)
Optional을 이용하여 안전한 코드를 짜려면 반드시 if 문으로 값이 있는지 확인을 해야 한다. 값이 있는 경우엔 ‘!’ 를 이용하여 값이 있음을 명시적으로 알려줘야 한다.
‘!’ 는 forced-unwrapping operator 로서 애매모호한 Optional Type 값을 명확하게 들어낸다. 이를 통해 불필요한 Optional Type 추가 발생을 막는다.
let numCards = ["one" : 1 , "two" : 2, "three" : 3, "four" : 4 , "five": 5] // dictionary[key] 는 optional type을 반환 (즉,myCard 는 Int?) let myCard = numCards["two"] if myCard { // 값을 체크 let card = myCard! // 값이 있으므로 !로 값을 드러냄 // card의 타입은 Int (Int? 가 아님) println("card \(card)") }else { println("no card") }
Runtime Error
Optional Type을 if 문 없이 unwrapping 하여 사용하면 Runtime Error가 발생한다. 더 이상 안전하지 않다.
let numCards = ["one" : 1 , "two" : 2, "three" : 3, "four" : 4 , "five": 5] let myCard = numCards["nine"] // myCard = nil println("\(myCard!)") // Runtime Error 발생
Optional Binding
결국 Optional Type을 다룰때는 항상
- 값 여부를 확인 하고
- 값이 있으면 unwrapping 하여
사용해야 한다.
그러나 매번 위처럼 단계적으로 처리하는 건 불편하다. Optional Binding은 이를 동시에 할 수 있다.
Optional Binding 포멧은 if let ~ 이다.
let numCards = ["one" : 1 , "two" : 2, "three" : 3, "four" : 4 , "five": 5] if let card = numCards["two"] { // 값 체크 & unwapping println("card \(card)") // card 는 Int 타입. }else{ println("no card") }
Optional Chaining
코드를 짜다보면 Optional Binding 이 중첩될 수 있다. 반복되는 if let~ 은 귀찮다.
여러 층의 Optional Binding을 한 줄에 쓰게 하는 것이 Optional Chaining이다. (Swift 개발팀에서도 Optional이 불편하긴 했나보다.)
Optional Chaining은 ‘?’ 기호를 사용한다. (Optional Type 선언에 사용하는 ‘?’ 과 동일시 하면 헷갈린다. 비슷하지만 다른것으로 보자.)
아래 예제 코드는 WWDC2014 에서 소개된 코드다.
// 중첩 Optional Binding if let home = paul.residence { if let postalAddress = home.address{ if let building = postalAddress.buildingNumber{ if let convertedNumber = building.toInt(){ println("building number of paul is \(convertedNumber)") } } } } // Optional Chaining if let addressNumber = paul.residence?.address?.buildingNumber?.toInt(){ println("building number of paul is \(addressNumber)")</pre> }
코드를 간단히 설명하면 paul 은 class 개체로 Non-Optional Type 이고 나머지 residence, address, buildingNumber 는 Optional Type 이다.
paul 의 집 주소를 알기 위해 Optioanl Binding으로 순차적으로 값을 확인한다. 이를 Optional Chaining 을 이용하면 한 줄에 끝이다.
Optional Chaining을 철도 선로로 이해하면 쉽다. 값이 없으면 nil 행이고 값이 있으면 keep going. (WWDC2014 비디오 스크린샷을 올리기가 좀 그렇지만, 좋은 설명이라 일단 첨부한다.)
Optional 과 Enum Pattern Matching
앞서 살펴봤듯이 Optional 은 enum 타입이다.
enum Optional<T>{ case None case Some(T) }
따라서 enum을 이용한 Pattern Matching 이 가능하다.
let numberCards : Dictionary<String,Int> = [ "one" : 1, "two" : 2, "three": 3, "four" : 4, "five" : 5, "six": 6, "seven" : 7] for num in numberCards.keys{ // Dictionary[key] 리턴은 optional type switch numberCards[num]{ case .Some(let v) where v > 3 : println("\(v)") default: println("...") } }
numberCards[num] 의 리턴값은 Optional Type 이므로 – 여기선 Int? – 위와 같이 switch, enum 을 이용하여 처리할 수 있다.
* Reference
- Intermediate Swift (WWDC2014 Video Session)
- The Swift Programming Language
yakmoz
/ June 25, 2014글 잘봤습니다. 감사합니다.
kwangsu
/ June 25, 2014잘 봐주셔서 감사합니다.
AllanCho
/ October 14, 2014좋은글 덕분에 배우고 갑니다 ^^
kwangsu
/ October 14, 2014감사합니다~
Hello
/ May 8, 2015최근 스위프트 보고 있는데 많은 도움 되었습니다^^!
kwangsu
/ May 8, 2015저도 요즘 다시 보고 있습니다 ㅎ
Thanks!
/ August 10, 2015Optional 이 왜 필요하지?
이런 궁금증을 가지고 있었는데 알려주셔서 감사합니다!
풀 (@mydayz)
/ November 13, 2015중간에 예제가 틀린 부분이 있습니다.
if myCard {
이 아니라
if (myCard != nil) {
이어야 하네요.
kwangsu
/ November 13, 2015네. 맞습니다. Swift 가 업데이트되면서 Optional type 의 불리언 정책이 바뀌었습니다. 제가 업데이트를 하지 못한 부분인데 상기시켜 주셔서 감사합니다. (조만간 최신 버전의 swift 를 기준으로 재정리할 예정에 있습니다.)