📱 Mobile/🚀 Swift

[Swift] 옵셔널 (Optional)

exception_log 2021. 1. 21. 20:02

<!— 본 포스트는 "꼼꼼한 재은씨의 Swift : 문법편"을 공부하며 직접 정리한 포스트입니다. —>

 

🚀작성자의 Swift 정리 원본인 Notion 구경하기 -> www.notion.so/Swift-59150070adb0467ea11d4f69090dbb24

 

 

옵셔널은 스위프트에서 도입된 새로운 개념으로서 언어 차원에서 프로그램의 안전성을 높이기 위해 사용하는 개념이다.
  • 옵셔널의 개념을 한 문장으로 정의하면 'nil을 사용할 수 있는 타입과 사용할 수 없는 타입을 구분하고, 사용할 수 있는 타입을 가리켜 옵셔널 타입 이라고 부른다.' 고 할 수 있다.
  • nil이란? 값이 없음을 의미하는 특수한 값
let capital = ["KR" : "Seoul", "CN" : "Beijing", "JP" : "Tokyo"]
capital["ko"] // nil
  • 스위프트에서는 값을 처리하는 과정에 문제가 있을 경우 많은 부분에서 오류를 발생시키는 대신 결과값을 nil로 반환한다. 하지만 모든 타입이 nil을 반환할 수 있는 것은 아니며 오직 옵셔널 타입만 nil을 반환할 수 있다. 다시 말해, nil을 반환하려면 해당 값이 옵셔널 타입으로 정의되어 있어야 한다.
  • 언젠가 오류가 발생할 수 있는 가능성이 조금이라도 있다면 옵셔널 타입으로 정의해야 한다.
  • 옵셔널 타입은 반환하고자 하는 값을 옵셔널 객체로 다시 한 번 감싼 형태를 의미한다.
Int("123") -> Optional(123) // 옵셔널 래핑
Int("안녕하세요") -> nil
  • 모든 값을 옵셔널 타입으로 선언하고 사용하면 편리하지 않을까?

    → 모든 값이 nil을 가질 수 있도록 한다는 것은 일일이 모든 값들의 nil 여부를 검증해야 한다는 뜻이다. 이는 프로그래밍 로직을 복잡하게 만들 뿐만 아니라 처리 과정 또한 어렵게 만든다. 그러므로 꼭 필요한 경우에만 제한적으로 옵셔널 타입을 적용하는 것이 좋다.

옵셔널 타입 선언과 정의

// 옵셔널 Int 타입
var optInt: Int?

// 옵셔널 String 타입
var optStr: String?

// 옵셔널 Double 타입
var optDouble: Double?

// 옵셔널 Array 타입
var optArr: [String]?

// 옵셔널 Dictionary 타입
var optDic: Dictionary<String, String>?
var optDic2: [String:String]?

// 옵셔널 Class 타입
var optClass: AnyObject?
  • 일반 자료형을 선언만 하고 초기화하지 않으면 아예 아무것도 할당되지 않지만, 옵셔널 타입으로 자료형을 선언하면 자동으로 nil 로 초기화된다. 물론 옵셔널 내부에 있는 자료형에 nil값이 부여된다는 것은 아니다.
// 옵셔널 타입의 변수와 상수에 값을 할당하는 방법
var optInt: Int?
optInt = 3
  • 옵셔널 타입에 값을 대입할 때에는 옵셔널이 아닌 일반 변수처럼 생각하고 다루어도 무방하다.

옵셔널 값 처리

// 옵셔널 타입은 결합 연산 또는 더하기 연산이 가능한 데이터 타입이 아니다.
Int("123") + Int("123") // X

// Int? 와 Int는 서로 다른 타입이므로 연산이 불가능하다
Int("123") + 30
  • 우리가 원하는대로 값을 연산하려면 옵셔널 객체를 해제해야한다. ( 옵셔널 언래핑 )
OptionL("123") -> (옵셔널 해제) -> 123 -> 값의 연산
  • 옵셔널 해제 방식은 명시적 해제묵시적 해제로 나누어진다. 그리고 묵시적 해제는 각각 컴파일러에 의한 자동 해제연산자를 사용한 자동 해제로 나누어진다.

옵셔널 강제 해제

  • 옵셔널 강제 해제 방법은 매우 단순하다. 옵셔널 타입의 값 뒤에는 '!' 기호만 붙여주면 된다.
// 옵셔널 타입의 변수 선언
var optInt: Int? = 3

print("옵셔널 자체의 값 : \(optInt)")
print("!로 강제 해제한 값 : \(optInt)")

// 실행 결과 >>
옵셔널 자체의 값 : Optional(3)
!로 강제 해제한 값 : 3

// ! 기호를 사용하면 연산도 가능하다.
Int("123")! + Int("123")! // 246
Int("123")! + 11 // 134
  • 그러나 실제로 값이 nil인 옵셔널 변수에 ! 연산자를 붙이면 오류가 발생한다. 그러면 왜 굳이 옵셔널 타입을 사용하지? → 그래서 옵셔널 변수나 상수를 안전하게 사용하려면 조건이 따른다.
  • 강제 해제 연산자를 사용할 때에는 먼저 옵셔널 값이 nil인지 점검해야 한다. 그리고 옵셔널 값이 nil이 아닐 때만 강제 해제 연산자를 붙여서 값을 추출해야 한다.
var str = "123"
var intFromStr = Int(str)

if intFromStr != nil {
	print("값이 변환되었습니다. 변환된 값은 \(intFromStr!)입니다.")
} else {
	print("값 변환에 실패하였습니다.")
}
// 실행 결과 >>
값이 변환되었습니다. 변환된 값은 123입니다.

var str = "Swift"
var intFromStr = Int(str)

if inFromStr != nil {
	print("값이 변환되었습니다. 변환된 값은 \(intFromStr!)입니다.")
} else {
	print("값 변환에 실패하였습니다.")
}

// 실행 결과 >>
값 변환에 실패하였습니다.
  • 위의 코드에서 intFromStr != str 구문을 잘 보면 비교연산자 != 사이에 의도적인 공백이 있음을 알 수 있다. 이 공백은 단순히 가독성을 위한 것이 아니다. 일반적으로 연산자 앞에 공백이 있을 필요는 없다. 왜냐하면 공백을 두지 않으면 두 가지로 해석될 여지가 있기 때문이다.
해석 1 : (intFromStr)!=(nil) -> 원래 의도한 대로 intFromStr 변수와 nil의 비교
해석 2 : (intFromStr!)=nil -> intFromStr 변수의 옵셔널 강제 해제 + nil 값의 할당

옵셔널 바인딩

조건식 대신 옵셔널 값을 일반 변수나 상수에 할당하는 구문을 사용하는 방식
  • 옵셔널 바인딩은 조건문 내에서 일반 상수에 옵셔널 값을 대입하는 방식으로 이루어진다. 반드시 조건문에서 사용해야만 하며, 상수에 옵셔널 값을 대입한 결과는 true / false로 리턴된다.
var str = "Swift"
if let intFromStr = Int(str) {
	print("값이 변환되었습니다.")
} else {
	print("값 변환 실패")
}
  • 위의 코드는 앞의 예시와 비슷하지만, intFromStr가 상수로 선언되었다는 점과 이 상수가 옵셔널이 아닌 일반 타입이라는 점이 다르다. 강제 해제 연산자를 사용하지 않아도 옵셔널 값이 일반 변수나 상수에 할당되면서 자연스럽게 옵셔널 타입이 해제되지만, 값이 nil이더라도 값의 할당이 실패하여 결과값이 false가 반환될 뿐이므로 오류는 발생하지 않는다. 단지 else 구문이 실행될 뿐이다.

옵셔널과 딕셔너리

앞서 다루었던 딕셔너리에 값을 입력하고 사용하는 예제를 다시 살펴보자.

var capital = ["KR" : "Seoul", "EN" : "London" , "FR" : "Paris"]

print(capital["KR"])
print(capital["KR"]!)

// 실행 결과 >>
Optional("Seoul")
Seoul
  • 딕셔너리에 키로 접근하면 그 결과값이 옵셔널 타입으로 반환된다. 그러나 위의 코드도 잘 구성된 코드는 아니다. nil여부를 체크하지 않고 ! 연산자를 사요하여 강제 해제를 실행한 것 때문이다. 코드를 보완해보자.
if capital["KR"] != nil {
	print(capital["KR"]!)
}

// 또는

if let val = capital["KR"] {
	print(val)
}

컴파일러에 의한 옵셔널 자동 해제

옵셔널 타입의 값을 사용하려면 항상 ! 연산자를 사용하여 옵셔널을 강제 해제 하든가, 아니면 옵셔널 바인딩을 통해 일반 자료형으로 바꾸어 주어야 한다. 하지만 명시적으로 강제 해제를 하지 않아도 컴파일러에서 자동으로 옵셔널을 해제해 주는 경우가 있다.

let optInt = Int("123")

if ((optInt!) == 123) {
	print("optInt == 123")
} else {
	print("optInt != 123")
}
  • 위의 예제는 문자열을 숫자로 변환한 옵셔널 상수의 값이 nil이 아니면 ! 연산자를 사용하여 옵셔널 값을 강제 해제하고 이 값을 정수 123과 비교하는 예제이다. 그리고 실행 결과는 optInt == 123 이다.
  • 이번에는 강제 해제하지 않은 옵셔널 타입과 정수 123을 비교해보자.
if (optInt == 123) {
	print("optInt == 123")
} else {
	print("optInt != 123")
}
  • 강제 해제하지 않은 옵셔널 값은 Optional(123)이므로 정수값 123과 다르다. 따라서 위 예제에서는 아마도 else 영역이 실행될 것이다. 그러나 실행한 결과는 optInt == 123 이다.
  • 왜일까? 옵셔널 객체의 값을 비교 연산자를 사용하여 비교하는 경우에는 명시적으로 옵셔널 객체를 강제 해제하지 않아도 한쪽이 옵셔널, 다른 한쪽이 일반 타입이라면 자동으로 옵셔널 타입을 해제하여 비교 연산을 수행한다. 따라서 아래의 경우 모두 true이다.
let tempInt = Int("123")

tempInt == 123 // true
tempInt == Optional(123) // true
tempInt! == 123 // true
tempInt! == Optional(123) // true

옵셔널의 묵시적 해제

옵셔널의 묵시적 해제는 비록 옵셔널 타입이긴 하지만 값을 사용할 때에는 자동으로 옵셔널이 해제되기 때문에 굳이 ! 연산자를 사용하여 해제할 필요가 없는 아주 편리한 구문이다. 컴파일러가 알아서 옵셔널을 해제해준다는 점에서 방금 다루어 본 자동 해제와 유사하지만, 자동 해제가 비교 연산이나 값의 할당 등 일부 구문에 한정되는 것과 달리 묵시적 해제는 옵셔널 변수를 사용하는 모든 경우에 적용할 수 있으며, 옵셔널 변수의 타입을 선언할 때 묵시적 해제를 미리 선언해 주어야 한다는 차이가 있다.

// 묵시적 옵셔널 선언
var str: String! = "Swift Optional"
print(str)

// 실행 결과 >>
Swift Optional

var str: String! = nil
// 이렇게 nil 값을 대입해도 아무 문제가 없다.

var value: Int? = 10
value + 5 // 오류

var value2: Int! = 10
value2 + 10 // 20
  • 모든 경우에 묵시적 옵셔널을 사용할 수 있는 것은 아니다. 변수의 값이 nil이 될 가능성이 있다면 묵시적 옵셔널 해제를 사용하지 않아야 한다.
  • 그런데, 변수가 nil 이 될 가능성이 있을 때 사용하는 것이 옵셔널 타입이라고 했는데, 변수가 nil이 될 가능성이 있다면 사용하지 말라니 무언가 이상하다. 그렇다면 언제 사용해야 한다는 뜻일까?
  • 묵시적 옵셔널 해제를 사용하는 경우는 한가지로 정의할 수 있다

    "형식상 옵셔널로 정의해야 하지만, 실제로 사용할 때에는 절대 nil 값이 대입될 가능성이 없는 변수일 때"

  • 옵셔널의 강점은 안전성 뿐 아니라 안정성을 담보하는 과정에서 표현되는 코드의 간결성에 있다.
반응형

'📱 Mobile > 🚀 Swift' 카테고리의 다른 글

[Swift] 함수 - (2)  (0) 2021.01.25
[Swift] 함수 - (1)  (0) 2021.01.22
[Swift] 집단 자료형  (0) 2021.01.20
[Swift] 흐름 제어 구문  (0) 2021.01.19
[Swift] Swift 기본 문법  (0) 2021.01.18