[Swift 정면돌파] 04. 옵셔널

본 시리즈는 아래 강의자료를 기반으로 작성되었습니다.
https://www.boostcourse.org/mo122/joinLectures/38564

오늘은 옵셔널에 관련하여 공부해보았다. Kotlin 의 Nullable 개념과 매우 흡사하며, Syntax 조차 별로 다를 바 없어 보였다. Null-Safety 의 편리함을 진작 깨달은 필자의 입장에서, 옵셔널 역시 유용하게 사용되겠구나 하는 생각을 해볼 수 있었다.

특히 앱 개발을 할 때, 미리 대응하지 못한 NPE 로 인한 ANL 발생을 막기 위한 수단으로 Null-Safety 특성이 큰 힘을 발휘한 통계자료가 있다. (Java → Kotlin 으로 공식 언어를 변경한 뒤 구글에서 ANL 횟수가 현저히 감소했다고 함) 따라서 iOS 앱 개발 시에도 이를 유용하게 사용해볼 수 있을 것 같다.

옵셔널 개념

→ 코틀린과 유사하게,스위프트 역시 기본적으로 nil 을 허용하지 않는다. 이는 Null-Safety 를 보장하기 위한 특성 중 하나이다. 개발자의 실수로 발생하는 NPE 를 방지해주는 엄청 편리한 특성이다. 따라서 스위프트에서 어떤 변수의 값이 nil 일 수도 있는 경우, 별도의 구분을 해주게 되는데 이것이 바로 '옵셔널' 이다. 옵셔널은, 값이 있을수도 있고 없을 수도 있는 변수를 의미한다. 별도로 nil 이 담길 가능성을 문서화 하지 않아도 사람이 읽기에 '아 이 변수는 nil 을 갖고 있을 수도 있겠구나' 하는 생각을 할 수 있다. 또한, 옵셔널 변수가 아닌 경우 예외처리를 하지 않아도 해당 변수가 nil 이 담길 가능성이 없는 것이기 때문에 안심하고 사용을 할 수 있다

func someFunction(someOptionalParam: Int?)
func someFunction(someParam: Int)

someFunction(someOptionalParam: nil)
someFunction(someParam: nil)  // 컴파일 오류 발생

옵셔널은 일반 변수들과 달리, Optional 이라는 enum 클래스로 래핑돼있는 형태이기 때문에 일반 변수처럼 사용할 수 없다. 갖고 있는 값에 대한 연산이 불가능하다.

var optionalValue: Int? = 100

switch optionalValue {
case .none:
    print("This Optional variable is nil")
case .some(let value):
    print("Value is \(value)")
}

// nil 할당 가능
optionalValue = nil

// 기존 변수처럼 사용불가 - 옵셔널과 일반 값은 다른 타입이므로 연산불가
//optionalValue = optionalValue + 1

따라서, 언젠가 nil 이 들어갈 수 있는 녀석이지만 자유롭게 일반 값 처럼 활용하고 싶다면 '암시적 추출 옵셔널' 이라는 구문을 사용하면 된다. 일반적인 옵셔널과 다르게, 데이터 타입 뒤에 ! 를 붙여 표현한다.
하지만, 암시적 추출 옵셔널 변수에 nil 을 넣으면 런타임 오류가 발생하므로 유의해야 한다.

var implicitlyUnwrappedOptionalValue: Int! = 100

switch implicitlyUnwrappedOptionalValue {
case .none:
    print("This Optional variable is nil")
case .some(let value):
    print("Value is \(value)")
}

// 기존 변수처럼 사용 가능
implicitlyUnwrappedOptionalValue = implicitlyUnwrappedOptionalValue + 1

// nil 할당 가능
implicitlyUnwrappedOptionalValue = nil

// 잘못된 접근으로 인한 런타임 에러 발생
//implicitlyUnwrappedOptionalValue = implicitlyUnwrappedOptionalValue + 1

그렇다면, 일반 옵셔널 같은 경우 변수의 값을 어떻게 사용할 수 있을까?
이에 대한 개념이 바로 '옵셔널 바인딩 (옵셔널 값 추출)' 이다.

옵셔널 값 추출

조금 번거롭지만, 아래 처럼 옵셔널 변수의 값을 추출하여 nil 여부를 판단할 수 있고, 만약 nil 이 아니라면 원하는 동작을 수행하도록 코드를 짤 수 있다. 대신, if-let 스코프 내에서만 추출한 값을 사용할 수 있다.

func printName(_ name: String) {
    print(name)
}

var myName: String? = nil

// printName(myName)
// 전달되는 값의 타입이 다르기 때문에 컴파일 오류발생
if let name: String = myName {
    printName(name)
} else {
    print("myName == nil")
}

var yourName: String! = nil

if let name: String = yourName {
    printName(name)
} else {
    print("yourName == nil")
}
// name 상수는 if-let 구문 내에서만 사용 가능
// 상수 사용 범위를 벗어났기 때문에 컴파일 오류 발생
// printName(name)

// ,를 사용해 한 번에 여러 옵셔널을 바인딩 할 수 있음
// 모든 옵셔널에 값이 있을 때만 동작 (&& 동작)
myName = "H43RO"
yourName = nil

// yourName이 nil이기 때문에 출력 구문이 실행되지 않음
if let name = myName, let friend = yourName {
    print("\(name) and \(friend)")
}

yourName = "LULU"

if let name = myName, let friend = yourName {
    print("\(name) and \(friend)")
}
// H43RO and LULU

하지만, if-let 구문을 사용하지 않고 다소 위험한 방식으로 옵셔널 내부의 값을 추출하는 방법도 있다. 바로 강제 추출 기법인데, 만약 옵셔널 변수가 nil 을 담고 있었다면 이를 사용할 시 런타임 에러를 뿜는다.

printName(myName!) // H43RI

myName = nil
// print(myName!)
// 강제추출시 값이 없으므로 런타임 에러 발생

yourName = nil

//printName(yourName)
// nil 값이 전달되기 때문에 런타임 에러 발생

따라서 필자는 강제 추출 기법같은 경우 디버깅 목적으로 사용하는 경향이 있다. 놓치는 예외처리가 없는지 점검할 때 이 강제 추출 기법을 사용하게 되면, 어떤 시점에서 nil 을 핸들링하는지 쉽게 알 수 있기 때문에 편리하다.

좋은 웹페이지 즐겨찾기