[Swift] 문법3 - 옵셔널 Optional

LeeEunJae·2023년 3월 16일
0

iOS

목록 보기
4/14
post-custom-banner

Swift 언어가 가지고 있는 가장 큰 특징 중 하나가 Optional 입니다.
직역하면 '선택적인' 이라는 뜻인데요. 값이 있을 수도 있고, 없을 수도 있는 상태, 즉 값이 있는지 없는지 확신할 수 없는 상태 입니다.

예를 들어보겠습니다. "가나다" 라는 문자열은 값이 존재한다고 할 수 있죠. 그럼 "" 는 값이 없는 것일까요? 아닙니다 ""도 엄연히 문자열이고, 값이 없다기보다는 빈 값이죠.
값이 없는 것은 Swift 에서는 nil 이라고 표현합니다. (왜 nil...? null 이라 안하고...?)

마찬가지로 빈 배열이나 빈 딕셔너리도 nil 이 아니고 비어있는 것 뿐입니다. 배열과 딕셔너리의 경우에도 값이 없는 경우 nil 로 표현합니다.

모든 값에 nil 을 넣을 수 있는 것은 아닙니다.

var number: Int = nil // error
var optionalNumber: Int? = nil // ok

값이 있을 수도 있고, 없을 수도 있는 변수를 선언할 때 타입 어노테이션에 ?를 붙여야 합니다.
이렇게 정의한 변수를 Optional이라 하고, 값을 초기화 하지 않으면 nil이 저장됩니다.

Kotlin의 Nullable 과 매우 유사하네요.

Optional로 정의한 변수는 일반 변수와는 다릅니다.
예를들어 다음과 같은 경우 오류가 발생합니다.

var optionalNumber: Int? = 1
var number: Int = optionalNumber // error

Optional 이 아닌 일반 Int 타입 변수에 Optional Int 타입 변수를 저장하려하고 있습니다.
number 입장에서는 optinalNumber 변수가 nil 이 있을지 실제 값이 있을지 명확하지 않은 상태에서 값을 초기화하려고 하고 있으니 안전하지 않은 접근이죠. 따라서 Swift 컴파일러는 안전을 보장하기 위해 위와 같은 접근을 제한하고 있습니다.

📌 옵셔널 바인딩 Optional Binding

그렇다면, 어떻게 해야 옵셔널 변수에 nil이 아닌 실제 값을 가져올 수 있을까요?
이때 사용하는 것이 '옵셔널 바인딩' 입니다.
옵셔널 바인딩은 옵셔널의 값이 존재하는지 검사하고, 존재한다면 그 값을 다른 변수에 대입해줍니다. "if let" 또는 "if var"를 사용하는데요. 옵셔널 바인딩을 통해 값이 존재한다면 if 문 안으로 들어가고, 아니면 그냥 통과하게 됩니다.

var optionalNumber: Int? = 1
if let number = optionalNumber { // optinalNumber의 값이 존재하면
    print(number) // 다음 출력문을 실행합니다.
}
// nil이면 그냥 통과

하나의 if 문에서 콤마(,) 로 구분하여 여러 옵셔널 바인딩을 할 수 있습니다.
이 때, 모든 옵셔널의 값이 존재해야만 if 문 안으로 진입할 수 있습니다.

var optionalName: String? = "이은재"
var optionalEmail: String? = "dldmswo1209@gmail.com"

if let name = optionalName, 
	let email = optionalName {
      // name 과 email 모두 값이 존재
      print("이름 : \(name) 이메일 : \(email)")
}

옵셔널 바인딩을 할 때, 콤마(,)로 조건을 지정할 수도 있습니다.
콤마 이후의 조건절은 옵셔널 바인딩 이후에 실행됩니다.
즉, 옵셔널이 벗겨진 실제 값을 가지고 조건절을 실행합니다.

var optionalAge: Int? = 22

if let age = optionalAge, age >= 20 {
    // age 값이 존재하고, 20 이상
    print("성인 입니다")
}

위 코드는 아래와 동일합니다.

var optionalAge: Int? = 22

if let age = optionalAge{
    if age >= 20{
        print("성인 입니다")
    }
}

📌 옵셔널 체이닝 Optional Chaining

옵셔널로 선언된 배열이 '빈 배열' 인지 확인하려면 어떻게 해야 할까요?
nil 이 아니면서 빈 배열인지 확인하면 될 것 입니다.

let optionalArray: [String]? = []
var isEmptyArray = false

if let array = optionalArray, array.isEmpty {
    isEmptyArray = true
} else {
    isEmptyArray = false
}

print(isEmptyArray)

이 때 옵셔널 체이닝을 사용하면 위 코드를 아래와 같이 간결하게 작성할 수 있습니다.

print(optionalArray?.isEmpty == true)

위와 아래 코드 둘 다 출력 결과는 true 입니다.
옵셔널 체이닝은 다음과 같이 옵셔널의 속성에 접근할 때, 옵셔널 바인딩하는 과정을 ? 키워드로 줄여주는 역할을 합니다.

optionalArray?.isEmpty 까지 실행 했을 때 반환되는 값은 Optional(Bool)로 Optional Boolean 값 입니다. isEmpty 의 반환 값은 Bool인데 옵셔널 체이닝으로 인해 Bool? 로 바뀐 것이죠.
따라서 반환되는 값의 경우의 수로 nil, true, false 가 있습니다.
그렇기 때문에 값이 실제로 true 인지 확인하기 위해 ==true 를 해주어야 합니다.

📌 옵셔널 벗기기

옵셔널을 사용할 때마다 옵셔널 바인딩 하는 것이 바람직합니다. 하지만, 개발을 하다보면, 값이 분명히 존재할 것임에도 불구하고, 옵셔널로 사용해야하는 경우가 있는데요. 이 때, 옵셔널에 nil 이 아닌 값이 존재한다는 가정하에 ! 키워드를 사용해 값에 접근할 수 있습니다.

var optionalName: String? = "eun jae"
print(optionalName!)

하지만, 예상과는 다르게 옵셔널 변수에 nil 이 있는 경우 ! 를 사용해서 강제로 옵셔널을 벗긴다면, 컴파일 에러가 발생하기 때문에 ! 키워드를 사용해서 옵셔널 값에 접근할 때는 주의가 필요합니다.

var optionalName: String?
print(optionalName!) // error!!

Fatal error: Unexpectedly found nil while unwrapping an Optional value
런타임 에러가 발생하면 iOS 는 앱을 강제 종료 시키므로 주의해야 합니다.

코틀린의 Nullable 변수를 강제로 접근할 때 !! 키워드를 사용하는 것과 동일하죠.
그래서 안드로이드 개발할 때에도 !! 키워드는 지양하는 편입니다.
Swift 역시 웬만하면, 옵셔널 바인딩을 통해 접근하는 것이 안전할 것 같습니다.

📌 암묵적으로 벗겨진 옵셔널 Implicitly Unwrapped Optional

옵셔널을 정의할 때 타입 어노테이션 뒤에 ? 대신 !를 를 붙이면, ImplicitlyUnwrappedOptional 라는 옵셔널로 정의됩니다. 직역하면 암묵적으로 벗겨진 옵셔널 입니다.

var optionalName: String! = "은재"
print(optionalName)

옵셔널 벗기기와 마찬가지로 값이 없는데 접근을 시도하면 런타임 에러가 발생합니다.

출력결과는 Optional("은재") 입니다.

Swift 2 버전에서는 "(email)"과 같이 문자열을 포맷팅하면 devxoul@gmail.com이 나왔으나, Swift 3 버전부터는 ImplicitlyUnwrappedOptional을 문자열 포맷팅 할 경우 Optional("devxoul@gmail.com")로 포맷팅되니 주의해서 사용해야 합니다. Swift 3 버전부터 ImplicitlyUnwrappedOptional을 일반 Optional과 거의 동일하게 취급했기 때문이랍니다...

Swift2 버전에서는 값에 바로 접근할 수 있는 장점이라도 있었지 이제는 그렇지도 않으니 사용할만한 이유를 모르겠네요.
게다가 런타임 에러가 발생할 수 있는 여지가 있는 방식을 굳이 사용할 것 같진 않습니다.
웬만하면 옵셔널 바인딩을 통해 런타임 에러를 피하는 것이 좋겠습니다.

profile
매일 조금씩이라도 성장하자
post-custom-banner

0개의 댓글