본문 바로가기

MOBILE/ios

[swift] swift의 optional을 슈뢰딩거의 고양이라고 생각하는 방법

swift에서 변수가 항상 값이 있을 수도 있지만, 또 값이 없는 경우가 있을 수 있다. 그럴 때는 어떻게 나타내야 할까?

A: nil이 값이 없음을 나타내니까 변수에 nil을 넣어보자!

라고 할 수 있다. 그럼 한번 넣어보자

 

var Value: String = nil

 

위 코드는 돌아갈까? 정답은 컴파일 오류다.

왜냐하면 우리는 선언할 때부터 swift에게 Value 변수는 String 타입이라고 알려줬는데 swift 입장에서 nil은 용납할 수 없기 때문이다.

그래서 swift는 optional 타입을 제공한다.


optional

쉽게 말하면 슈뢰딩거의 고양이다.

optional 키워드를 지정함으로써 이 변수에 값이 있을 수도 있고, 없을 수도 있다는 것을 swift에게 알려준다.

마치 상자 안을 관찰하기 전까지는 고양이가 삶과 죽음의 상태를 모두 지니고 있다는 것처럼

이 변수도 값이 있는 상태와 없는 상태 둘 다 유지한다고 생각하면 쉽다.

이 부분을 공부했을 때 값의 존재 상태를 알려준다는 부분이 왠지 굉장히 양자역학적이라고 생각했다...

optional을 지원하는 키워드는 두가지이다.

! ?

이 키워드들은 성질이 약간 다르다.

먼저 물음표부터 알아보자


?

데이터 타입 뒤에 물음표를 붙여서 이 변수가 상자 속 슈뢰딩거의 고양이 상태에 있다는 것을 알려준다.

즉 Value는 값이 있을 수도 있고, 없을 수도 있다는 것이다.

var Value: Int? = nil

정확하게 말하자면 optional은 enum+generic이다. 또한 열거형이라서 switch구문에서 사용 가능하다.

 

var optionValue: Int? = 100
switch optionValue {
case .none:
    print("this is nil")
case .some(let value):
    print("value is \(value)")
}

 


!

슈뢰딩거의 고양이는 양자역학적으로 의미가 큰데, 관찰하기 전까지 상태를 알 수 없다는 것이다.

느낌표로 표현하는 optional은 여기서 "관찰"을 의미한다.

즉 상태를 알아내는 시점이라는 것이다. 이를 swift에서는 unwrapping이라고 한다.

물음표가 슈뢰딩거의 고양이 상자를 밖에서 보는 것이라면, 느낌표는 상자를 열어보는 관찰의 행위를 통해 상태를 확정짓는다는 것이다.

뭔 소리냐면 한마디로 값이 정해진다는 것이다.

그 값은 있을 수도 있고 없을 수도 있다.

var value1: Int! = nil // value1에는 nil이 들어간다.
value1 = 4 // value1에는 4가 들어간다.

 

그니까 물음표를 써서 나타낸다면 값이 공존할 수 있지만 느낌표는 정해져버린다.

여기까지 봐서는 뭔 소리인지 모를 수 있다.

이럴 때는 예시를 통해서 알아보는게 최고다. 다음의 예시를 보자.

 

var value1: Int? = 4
var value2: Int! = 5

var value3 = value1 // 가능 -> optional 타입이 됨
var value4 = value2 // 가능 -> Int 타입이 됨

var value5: Int = value1 //불가능
value5 = value2		// 가능

 

위를 이해했다면 거의 이해한 것이다.

 

value1은 물음표로 선언했고, value2는 느낌표로 선언했다. 

즉 value1은 물음표로 선언했기 때문에 optional 타입이다. 따라서 value3에 이 값을 대입하면 value3는 optional타입이 된다.

value2는 느낌표로 선언했다. 따라서 unwrapping을 시작해서 5를 봤더니 어라 정수네? 하고 value2를 정수로 생각한다. 따라서 value4에 이 값을 대입하면 value4는 Int타입이 된다.

 

위와 같은 논리로 value5를 int라고 swift에게 알려줬는데 물음표로 선언한 value1은 한마디로 슈뢰딩거의 고양이상태이기 때문에 타입이 맞지 않아 오류가 뜬다.

하지만 느낌표로 선언한 value2는 관찰 즉 swift에서는 unwrapping이 진행되었기 때문에 Int타입임을 알고 있다. 따라서 value5에 대입해도 문제가 생기지 않는다.

 

하지만 value1의 값을 꼭 무조건 value5에 복사해야 한다면?

느낌표 키워드로 unwrapping을 진행할 수 있다. (언래핑 진행시켜)

 

value5 = value1!

 

이렇게 상자속에 갇힌 고양이를 관찰 즉 강제로 unwrappingg하여 값을 가져온다.

이를 forced unwrapping이라고 한다. 


optional 추출

optional의 값을 확인하는 방법은 크게 세가지이다.

 

1. optional binding

2. optional chaining

3. forced unwrapping 

 

차례차례 살펴보자.

 

1. optional binding

한마디로

"잠시 검문 있겠습니다. 후~ 불어주세요."

같은 느낌이다. 즉 optional 변수의 값을 보고 nil이면 입구컷을 시킨다는 뜻이다.

이를 위해 swift는 if let 구문을 지원한다.

 

// 숫자를 출력하는 함수를 만들자
func numbering(_ num: Int) {
	print("The number is \(num)")
}

// optional로 선언된 변수
var MyNum: Int? = 4

// numbering(MyNum) -> 불가함.

 

optional로 선언된 변수를 numbering함수의 파라미터로 넣을 수 있을까?

위에서 알아본 것과 같이 타입이 다르기 때문에 불가하다. 하지만 난 꼭 파라미터로 전달해야 한다구요. 그렇다면 if let 구문을 쓸 수 있다.

 

if let number: MyNum {
  numbering(number)
}
else {
  print("nil")
}

 

위와 같이 쓰면 optional 타입인 MyNum 변수를  number에 복사하고 numbering함수를 실행한다.

MyNum가 nil값이라면 else구문이 실행되는 것은 당연하다.

 

2. optional chaining

미분적분학 시간에 chain rule에 대해 배운 사람은 키워드만 보고도 약간 감을 잡을 것이다. 아니면 딥러닝을 공부한 사람들은 경사하강법 편미분 공식을 유도할 때 chain rule을 신나게 했을 것이니 감이 올 수 있다.

말 그대로 optional 값을 확인할 때 chain rule을 사용하겠다는 것이다. 하위로 내려가면서 연속적으로 값을 확인하는 데 이때 하나라도 nil값이 있다면 전체가 nil을 반환한다.

optional chaining을 설명하는 아주 좋은 코드가 있어서 가져와봤다.

class Person{
    var residence: Residence?
}
class Residence{
    var roomCount = 1
}
let _person = Person()
if let roomCount = _person.residence?.roomCount{
    print("room: \(roomCount)")
}
else {
    print("this is nil")
}

 

위와 같은 상황에서 콘솔에 어떤 로그가 찍히게 될까? 답은 this is nil이다.

먼저 person 클래스의 residence 변수에 Residence 클래스를 optional 타입으로 할당했다. optional 타입은 초기화를 안하면 nil로 초기화된다. 따라서 현재 residence 변수는 nil값이다.

if let 구문을 보면 roomCount 변수에 .residence와 그 하위의 .roomCount 를 모두 체크하고 있음을 알 수 있다. 이 때 .residence 뒤에 물음표가 붙는 이유는 값이 있을 수도 있고 없을 수도 있는 optional이기 때문이다.

구문 체크를 할 때 .residence를 unwrapping하게 되고 그 값이 nil임을 알았으니 당연하게도 일행 모두를 입구컷시킨다. 따라서 this is nil이 반환된다.

 

3. forced unwrapping

위에서 본것과 같이 느낌표 키워드를 사용해서 강제로 상자를 철거하는 작업이다.

근데 이 작업은 안정적이지 못하다. 실제로도 swift 가이드 문서를 보면 느낌표 키워드는 아주 신중하게 사용하라고 되어있다.

항상 이 값이 nil이 아님을 확신할 수 있을 때만 사용하라는 것이다.

 

var number: Int? = 4
print(number!)

number = nil
print(number!) /// 경고

 

위와 같은 상황이 발생할 수 있기 때문에 항상 주의 깊게 권장하는 방법은 아니라는 것이다.