[Swift] 옵셔널과 옵셔널 언래핑

swimmer_o3o·2023년 1월 10일
0
post-thumbnail

오늘은 Swift의 옵셔널에 대해서 알아보는 시간을 가져봅시다!

옵셔널 (Optional)


옵셔널이란, cpu가, 값이 없는 메모리 영역에 접근했을 때 발생하는 에러를 방지하기 위해 생긴 새로운 타입이다.

var strOpt: String? = nil 
var strOpt2: String? = "안녕하세요" // String 옵셔널 타입은 nil 값과 모든 String 타입을 포괄하는 데이터 타입이다.

옵셔널의 표현과 값의 범위

기존에 존재하는 타입에 ‘?’을 붙인 형태로 표현하며,

해당 타입의 모든 데이터 값과, ‘값이없음’을 표현하는 키워드인 ‘nil’을 포함하는 데이터들을, 쉽게 말하자면 하나의 겹으로 감싼 임시 데이터 형태의 타입이다.

var opt = nil // 에러, 옵셔널 변수의 선언은 묵시적으로 할 수 없다. 
var opt: Int? = nil

위 코드의 둘째줄과 같이, Int 의 옵셔널 타입을 ‘명시적’ 으로 선언해주었다. 첫째줄과 같이 옵셔널 변수의 경우 묵시적으로 선언 할 수 없는데, 그 이유는 묵시적으로 선언 후 nil 값을 할당할 경우, 어떤 타입의 옵셔널 인지 알 수 없기 때문이다! (Swift는 타입에 엄격한 언어라구! 😤)

옵셔널 값의 출력

print 값을 통해 옵셔널 변수를 출력하면 어떻게 표현될까?

var opt_1 : Int? = 3
print(opt_1) // Optional(3) , 값이 옵셔널 이라는 임시 데이터 형태로 싸져있다.

// 옵셔널 값을 복사해 새로운 변수에 담는다면?
var a : Int? = 3
var b = a
print(b) // Optional(3)

먼저 위 코드처럼 ‘옵셔널 안에 실제 값이 존재하는’ 옵셔널 변수를 출력할 경우, 콘솔창에는 Optional(3) 이라고 표현된다, 즉 옵셔널 변수는 그 값이 임시 데이터 형태 안에 감싸져 있는 형태로 출력되어, 바로 그 값에 접근 할 수 없다.

var optionalStr : String?
print(optionalStr) // nil, 초기화 되지 않은 옵셔널의 기본 값은 nil 이다.

만약 위 코드처럼, 옵셔널 선언시 초기화 되지 않는다면, 기본값으로 nil이 출력된다.

옵셔널을 선언하는 방식

var newOpt : Int? // 간편한 표기
var realOpt : Optional<Int> // 정식 표기

// 옵셔널은 반드시 var로 선언한다.
let letOpt : Int? = nil // 이 경우 옵셔널변수에 값이 들어올 수 있는 가능성이 전혀없기 때문에 옵셔널을 상수로 선언하면 안됨.
var varOpt : Int? = nil // 옵셔널은 변수로 선언하는것이 바람직.

위 코드와 같이 옵셔널을 선언하는 방법으로는, 주로 사용하는 방법인 [타입명]? 외에도, Optional<[타입명]> 으로 적어주는 방식이 있다.

위에서 설명한 바와 같이, 옵셔널은 그 자체로 연산에 활용하거나, 값에 접근할 수 없기 때문에, 반드시 ‘값을 추출’ 하는 옵셔널 언래핑 (Optional Unwrapping) 과정을 거쳐서 값을 직접 빼내줘야한다.

옵셔널 언래핑 (Optional Unwrapping)


옵셔널 타입으로부터, 그 안에 값이 들어 있다면 (nil이 아닐 경우) 그 값을 추출하는 옵셔널 언래핑 방식중 대표적인 3가지를 알아보자.

(1) 강제 추출 (Forced Unwrapping)

(2) ⭐️ 옵셔널 바인딩 (Optional Binding)

(3) 닐 코얼레싱 (Nil - Coalescing)

(1) 강제 추출 (Forced Unwrapping)

첫번째 방식인 강제 추출은, 옵셔널 밖으로 강제로 값을 꺼내는 방식으로, 옵셔널이 nil 이 아니라는 사실을 확신 할 수 있을 경우 사용한다. 강제추출 연산자인 ‘!’을 사용한다.

var fu : Int? = 5
print(fu!) // 옵셔널 fu 를 강제추출, 5

만약 nil인 옵셔널을 강제추출 할 경우 에러가 발생하기 때문에, 반드시 옵셔널이 nil이 아니라는 확인이 있을 때 사용한다.

강제추출을 보다 안전하게 사용하기 위해, 먼저 if 조건문을 사용해, 옵셔널이 nil이 아니라는 것을 먼저 확인한 후(조건문을 통해 검사해 강제추출의 실패 가능성을 배제), 옵셔널을 강제 추출하는 방식도 존재한다.

if fu != nil {
    print(fu!)
}

위 코드와 같이 간단하기 fu != nil 을 통해 nil이 아님을 검증한다.

(2) 옵셔널 바인딩 (Optional Binding)

마지막으로, 가장 많이 권장되고 자주 쓰이는 옵셔널 바인딩 방식이 있다. 옵셔널 언래핑은 기본적으로 해당 옵셔널 변수가 ‘값’을 가지고 있다면, 그 값을 추출하는 컨셉이며, 옵셔널 바인딩은 이를 검사하기 위해, 다른 옵셔널이 아닌 변수를 선언하고, 이 변수에 옵셔널을 대입 (=바인딩) 하는 방식을 취한다.

즉, 조건문을 활용해 옵셔널이 특정 변수(통상적으로 let으로 임시상수를 사용한다)에 바인딩이 된다면, 그 바인딩 된 변수의 값을 활용하여 이하 작업을 진행한다.

  1. if-let 구문을 활용한 옵셔널 바인딩.
if let bind = fu {
    // 옵셔널인 fu 가 bind 에 바인딩 된다면
    // nil이 아니라는 의미.
    print("바인딩 완료 , -> \(bind)")
} 
else {
		print("바인딩 실패, nil 입니다.")
}

if-let 구문을 활용하는 ****옵셔널 바인딩의 경우 임시상수에 옵셔널을 바인딩 시도하고, if 조건문을 활용해, 바인딩을 성공할 경우의 동작을 먼저 분기 처리 해주는 방식이다. 위 코드와 같이 if 조건문 안에 바인딩 완료시 동작을 먼저 명시해준다.

  1. guard - let 구문을 활용한 옵셔널 바인딩.
func guardLetBind (_ input : String?){
    
    guard let bind = input else { // 바인딩 실패의 경우 분기처리
        print("바인딩 실패")
        return
	   }
    print ("바인딩 완료, -> \(bind)") // 바인딩 성공의 경우 분기처리.
    
}

let opt1 : String? = nil
let opt2 : String? = "냐냐"

guardLetBind(opt1) // 바인딩 실패
guardLetBind(opt2) // 바인딩 완료, -> 냐냐

guard-let 구문을 활용하는 ****옵셔널 바인딩은 guard 문을 사용하는 옵셔널바인딩 방식으로, 실제로 앱을 만들때 많이 사용 하는 방식이다. 함수 안이나 조건문 안 에서 사용되는 guard 문을 활용하며, 때문에 위 코드와 같이, 바인딩이 실패할 경우를 먼저 분기처리 해주고, 그 외의 경우를 바인딩 완료된 경우로 취급한다.

(3) 닐 코얼레싱 (Nil - Coalescing)

닐 코얼레싱은, 옵셔널 표현식의 바로 뒤에, 해당 옵셔널이 nil일 경우 채택할 수 있는 기본값을 제시하여, 옵셔널 변수가 nil일 가능성을 없애는 방식이다. (즉 옵셔널 자체의 가능성을 없애는 방법이다.)

var oneOpt: String? = nil
var twoOpt: String? = "나는 값이 있다네"

// 닐 코얼레싱
var nilCoalesced1 = oneOpt ?? "디폴트" 
var nilCoalesced2 - twoOpt ?? "디폴트"

print(nilCoalesced1) // 디폴트
print(nilCoalesced2) // 나는 값이 있다네.

닐 코얼레싱은 [옵셔널 변수] ?? [기본값] 으로 표현하며, 위 코드와 같이 선언시 nil로 초기화 한 변수 oneOpt 를 "디폴트" 라는 초기값을 가지도록 닐 코얼레싱 한 값을 다른 변수 nilCoalesced1 에 바인딩 할 경우, nilCoalesced1는 “디폴트” 를 가진다. 반면 twoOpt는 값이 존재하는 옵셔널 변수로, 이를 닐 코얼레싱 하면 기존 옵셔널의 값이 바인딩이 된다.

oneOpt ?? "디폴트" // oneOpt가 값을 가질 경우에는 해당 값을, nil일 경우 "디폴트"를 나타낸다.

// 참고 - 삼항 연산자를 통한 닐 코얼레싱의 로직 구현 

var justOpt : String? = nil
var coal = justOpt != nil ? justOpt! : "디폴트"

닐 코얼레싱은 삼항 연산자와 비슷하게, 조건에 따라 값 자체를 분기할 수 있다. 위 코드처럼 닐 코얼레싱의 로직 자체를 삼항 연산자로 구현할 수도 있으며, 이를 통해 닐 코얼레싱의 동작에 대해 이해할 수 있다.

이 외에도, 옵셔널 체이닝 (Optional Chaining) 이라는 방법이 있는데, 이에 대해서는 추후 클래스, 구조체 등의 커스텀 타입에 대해 공부하고 난 후 정리해 보겠다.

참고 ) 옵셔널을 함수의 파라미터로 활용할 때


옵셔널도 하나의 타입이기 때문에, 함수의 파라미터 타입으로 활용될 수 있는데, 애플이 구현해둔 많은 내장 함수들이 옵셔널 타입의 파라미터를 가진다. 이때, 아래와 같이 옵셔널 파라미터의 경우, 미리 nil로 초기화 해두는 것이 일반적이다.

func doSomePrint(with label : String, name : String? = nil) {
  print("\(label): \(name ?? "없음")") // 닐 코얼레싱으로 구현
}

그 이유는 옵셔널로 파라미터를 받는 경우, 대부분 해당 파라미터의 아규먼트가 값이 없는 경우, 즉 파라미터가사용되지 않는 경우를 염두한 것인데, 이처럼 굳이 값을 넣고 싶지 않을 때, 함수 호출시 해당 파라미터를 명시하는 것이 귀찮기 때문이다.

위와 같이 특정 옵셔널타입 파라미터를 0으로 초기화 하며 선언된 함수는

doSomePrint(with : "지우")
doSomePrint(with : "지우", name : "Swimmer")

이처럼 함수 호출 시 해당 옵셔널타입 파라미터를 생략 가능하다.

다음시간에는 Swift의 컬렉션(Collection)타입에 대해 정리해 보도록 하겠다. 🙇🏻‍♀️

profile
서비스 기획과 개발을 아우르며, IT 생태계를 유영하는 물고기🐠. 무한한 가능성이 잠재된 IT의 바다속으로 깊게 잠수하는 큰 고래🐋 로 성장하고 싶습니다.

1개의 댓글

comment-user-thumbnail
2023년 4월 21일

너무 유익한 게시글이였어요 ^^

답글 달기