본문 바로가기

Swift

[Swift] Optional 제대로 알기

C나 Objective C에는 없는 개념인 Optional에 대해 알아보도록 하겠습니다. Optional은 Generic Enumeration으로 선언되어 있으며, wrapped valuenil을 나타내는 타입입니다. Optional은 해당 변수에 값이 없을 것(nil) 같을 때 사용할 수 있습니다.

 

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber는 Int?나 Optional<Int>로 형식이 추론됩니다.

 

String인 "123"을 Int로 캐스팅하면 당연히 123이 나오겠다고 생각하지만 캐스팅은 실패할 수도 있습니다. 그렇기 때문에 캐스팅 결과는 Optional 타입으로 반환되게 됩니다. Optional 타입은 간단하게 타입뒤에 물음표를 붙여 나타낼 수 있습니다.

 

let shortForm: Int? = Int("42")
let longForm: Optional<Int> = Int("42")

 

Optional Type은 두 가지 case를 가진 Enumertaion입니다. nil을 의미하는 Optional.none과 wrapped value를 저장하고 있는 Optional.some이 있습니다.

 

let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none
print(noNumber == nil) // true

 

결론적으로, Optional 타입의 변수에는 nil이 할당될 수 있고 Non-Optional 타입의 변수에는 nil이 할당될 수 없습니다.

 

var serverResponseCode: Int = 404
serverResponseCode = nil // 불가능

var serverResponseCode: Int? = 404
serverResponseCode = nil // 가능

var surveyAnswer: String? // 자동으로 nil 세팅

 

Java를 경험해 본 적이 있다면 NullPointerException을 방지하기위한 방어로직을 본적이 있을 것입니다. Optional은 이런 Runtime Error를 Compile 단계에서 막을 수 있는 매우 유용한 기능입니다.

 

// Optional을 사용하지 않는다면 아래와 같이 처리해야함
if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}

 

이제, Optional을 활용해보도록 하겠습니다.

Optional은 값을 감싸고 있는 상자라고 생각하면 편합니다.

 

상자안에 있는 값을 꺼내기 위한 방법은 2가지가 있습니다.

  1. 상자안에 값이 있는지 확인한 후에 꺼내는 방법
  2. 강제로 상자를 뜯어서 꺼내는 방법

활용 방법을 소개하기 전 예시에 필요한 이미지에 대한 정보가 담겨있는 Dictionary를 하나 선언하도록 하겠습니다.

let imagePaths = ["star": "/glyphs/star.png",
                  "portrait": "/images/content/portrait.jpg",
                  "spacer": "/images/shared/spacer.gif"]

Optional Binding

앱이 죽지 않도록 안전하게 값을 꺼내서 사용해보도록 하겠습니다. Optional Binding에는 if-let, guard-let, switch를 활용할 수 있습니다.

 

// if-let 구문
if let starPath = imagePaths["star"] {
    print("The star image is at '\(starPath)'")
} else {
    print("Couldn't find the star image")
}

// guard-let 구문
guard let starPath = imagePaths["star"] else { return }
print("The star image is at '\(starPath)'")

// 모두 "The star image is at '/glyphs/star.png'" 가 출력됨

Optional Chaining

wrapped instance의 프로퍼티나 메소드에 안전하게 접근하기 위해서 optional chaining을 활용할 수 있습니다.

 

if imagePaths["star"]?.hasSuffix(".png") == true {
    print("The star image is in PNG format")
}
// "The star image is in PNG format"가 출력됨
// imagePaths["star"]가 nil이었다면 바로 비교를 종료함

Nil-Coalescing Operator

해당 변수가 nil일 경우 nil-coalescing operator(??)를 사용하여 default value를 지정해줄 수 있습니다.

 

let defaultImagePath = "/images/default.png"
let heartPath = imagePaths["heart"] ?? defaultImagePath

print(heartPath) // nil이므로 기본값으로 지정한 "/images/default.png"이 출력

Unconditional Unwrapping

Optional 타입의 변수에 nil이 절대 할당되지 않을 것이란 확신이 있을 경우에는 상자를 강제로 뜯는 forced unwrapping을 사용합니다. 변수뒤에 느낌표를 붙여서 사용하면 됩니다.

 

let number = Int("42")!
print(number) // 42

let isPNG = imagePaths["star"]!.hasSuffix(".png")
print(isPNG) // true

 

강제로 뜯어서 사용하는 만큼 편하지만 매우 위험한 방법입니다. 만약 해당 변수에 nil이 할당되어 있을 때 forced unwrapping을 사용할 경우 Runtime Error가 발생하여 앱이 강제 종료 되기 때문입니다.

선언단계에서 implicitly unwrapped으로 선언할 수도 있습니다.

 

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 느낌표가 필요
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 느낌표가 필요하지 않음

정리

  • Optional Type은 2가지의 case를 가지는 Generic Enumeration 입니다.
  • Optional Value를 사용할 때는 다양한 방법으로 안전하게 꺼내어 사용해야 합니다.
  • Forced Unwrapping(!)을 사용할 경우 Runtime Error의 위험이 존재합니다.

태그