swift - 꼼꼼하게 다시 정리해본 옵셔널 개념(Optional)
05 Mar 2020 | swift개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
Optional
우리가 일반적으로 아는 영어뜻처럼 Optional이라는 것은 선택적인 이라는 뜻을 가진다. 선택적인 이라는 단어뜻처럼 이는 스위프트에서도 있어도 되고 없어도 된다는 의미를 가진다. 즉, 코딩을 하면서 어떤 변수에 값이 있을수도 있고 없을 수도 있는 경우를 위해서 존재한다. swift 개발을 할때 ? 혹은 !
기호를 자주 봤을텐데, swift에서는 이 기호들을 타입 어노테이션 옆에 붙여줘야 한다.
var test: Int
test = nil
위 간단한 코드를 봐보도록 하자.
반드시 에러가 뜰 것이다 > nil은 Int형 태입에 할당될 수 없다.
swift에서는 기본적으로 변수를 선언할 때 non-optional인 값을 할당해주어야 한다. 즉 어떠한 값을 변수에 반드시 할당해주어야 하는데 위 코드에서는 우리가 변수에 Int형으로 변수를 선언하였음에도 불구하고 test라는 변수에 nil을 할당해주었기 때문에 에러가 뜨게 된것이다.(nil은 어떠한 값이 아니기 때문에 발생하는 에러) 따라서 애초에 Int 타입에 Int 타입이 아닌것을 넣었기 때문에 Int라는 메모리 공간이 초기화되지 못한 것을 의미한다.
그러나 우리가 코딩을 하다보면, 반드시 값이 그때그때 할당되지 않는 경우도 있을 것이다.
즉, 변수안에 값이 확실히 있다는 것을 보장할 수 없을때 Optional을 사용한다.
var test: Int?
test = nil
이제 위에처럼 코드를 변경해보자. 에러가 사라졌을 것이다. 이때 ?가 아닌 !를 사용해도 에러는 사라질 것이다(optional기호를 사용했기 때문)
이 변수 안에 값이 있을수도 없을수도 있다는 것을 명시해줌으로써 optional변수는 초기화하지 않으면 nil로 자동 초기화가 되기 떄문에 위 같은 코드도 정상적으로 동작하는 것을 볼 수 있다.
optional값을 만들때 우리는 아래와같은 상자를 생각해보면 이해하기 쉬워진다.
?
옵셔널로 변수를 선언했고 그때 ?를 사용했다면 우리는 ‘노크’를 했다고 생각하면 된다.
이때 만약 값을 가지고 있다면 해당하는 값을 반환해주고 값이 없다면 nil을 반환하게 된다. 이때 nil 또한 메모리를 차지하고 있다.
var someValue: Int? = 30
var Value = someValue
이때 위 코드에서 Value의 타입은 무엇일까? 바로 옵셔널이다.
즉, Value는 옵셔널 타입이며 Int데이터형을 가질 수 있는 변수를 의미한다. 이는 Int일수도 있고 Int가 아닐수도 있다.
var someValue: Int? = 30
var Value: Int = someValue
그런데 만약 위처럼 Value에 Int 타입을 지정해줬다고 해보자. 오류가 뜨게 될 것이다.
기본적으로 Int와 Int?는 다른 타입이다.
someValue는 옵셔널타입으로 값을 가질수도 있고 nil을 가질수도 있다. 그런데 Value에는 무조건 Int 타입만을 가질것이라고 선언을 해줬다. 그러니 당연히 Value는 someValue를 받아들이지 못할 것이다. 따라서 Int와 Int? 즉 옵셔널인 것과 옵셔널이 아닌것은 완전히 다른 타입을 의미하는 것을 알아야 한다.
!
!는 ?와 다르게 강제로 값을 빼내오는 것을 의미한다. 유명한 짤로 망치로 상자를 부수는 것이 떠오를 것이다.
이는 강제 언래핑(Unwrapping)이라고 하고 이 단어가 의미하는 것은 상자안에 값이 있든 없든 무조건 값을 가져오겠다는 것을 의미한다.
깨부순 상자안에는 값이 있을수도 있고 없을 수도 있을 것이다.
var someValue: Int? = 30
var Value: Int = someValue
위에서 본 예제를 다시 한번 봐보도록 하자. 오류가 났던 위 예제를 아래처럼 고쳐보도록 하자
var someValue: Int? = 30
var Value: Int = someValue!
자. 강제로 상자를 부쉈기때문에 에러는 발생하지 않았을 것이다. 그리고 다행히 상자 안에는 30이라는 값이 있었기 때문에 30이라는 숫자또한 값을 가지게 될 것이다. 그렇기 떄문에 옵셔널이 아닌 Int형의 Value라는 변수안에 데이터(30)이 들어갈 수 있게 될 것이다. 상자를 부수고, 값을 꺼내어 그 값을 Value안에 넣어준 코드이니 Value입장에서는 문제가 없을 것이다. (Value는 Int형 데이터만을 받고 있는데 운이좋게 someValue에는 30이라는 Int형의 데이터가 있었기 때문이다)
그런데 만약 상자안에 값이 없다면? 생각해보자
var someValue: Int? = nil // someValue는 옵셔널타입으로 nil이 들어갈 수 있다
var Value: Int = someValue!
컴파일 오류도 일어나지 않을 것이며 build success도 되겠지만 금방 런타임에러가 발생하게 될 것이다.
!를 사용해 값이 존재하지 않는 옵셔널 값에 접근하려 시도하면 런타임 에러가 발생한다.
느낌표를 사용해 강제 언랩핑을 하기 전에는 항상 옵셔널값이 nil이 아님을 확실히 해야한다.
즉, nil이 아니라는 것이 확실하지도 않은 상태에서 !를 남용하면 에러가 날 확률도 높아질 것이다. 그리고 !도 옵셔널이기 때문에 초기화할 때 갑을 요구하지 ㅇ낳는다. 초기화해주징낳으면 ?와 마찬가지로 nil값이 들어가게 될 것이다. 잊지말자
그러면 이제 옵셔널 타입의 변수값을 가져오는 방법에 대해 생각해 보자
옵셔널 바인딩(Optional Bounding)
옵셔널 바인딩은 주로 if let(if var)구문과 같이 사용된다. 즉 먼저 체크해준다 를 생각하면 이해하기 쉽다.
이 값이 nil인지, 값이 있는지 경우에 따라 결과를 달리하고 싶을때 옵셔널 바인딩을 사용해 겁사해주면 된다.
func printName(_name:String) {
print(_name)
}
var myName: String? = nil
if let name = myName {
printName(_name: name)
}
실행해보면 콘솔창에는 아무것도 나오지 않을 것이고(myName은 nil값으로 초기화되어있기 때문) 이는 if let구문이 myName이라는 상자에 노크해보고 값이 있으면 name에 넣어주고 조건문을 실행해 라는 것을 의미한다는 것을 알 수 있을 것이다. 이때 값이 있는 경우에만 값이 바인딩 되기 때문에 콘솔창에는 아무것도 실행되지 않아 비어져있을 것이다.
옵셔널 체이닝(Optional Channing)
옵셔널 체이닝은 하위 프로퍼티에 옵셔널값이 있는지 없는지를 연속적으로 확인하면서 중간에 하나라도 nil이 존재함다면 nil이 반환되는 형식을 의미한다.
let roomCount = zehye.residence?.numberOfRoomes
위처럼 진행하는 방식이 옵셔널 체이닝이다. 이때 zehye의 residence가 nil이 아니라면 다음으로 넘어가 residence의 numberOfRoomes를 확인하게 될 것이다. 만약 zehye의 residence가 nil이라면 뒤의 else문을 수행하게 될 것이다. 그런데 이때 residence 뒤에 왜 ?가 붙을까?
그 이유는 residence가 nil을 반환할 수도 있고 아닐수도 있기 때문이다.
즉 하위 프로퍼티에 옵셔널 값이 있는지 연속적으로 확인하면서 중간에 하나라도 nil이 발견된다면 nil을 반환하는 것을 옵셔널체이닝 방식이라고 한다.
그러면 이때 roomCount의 타입은 무엇일까? 당연히 옵셔널 타입일 것이다. 즉 우리가 특별히 지정해주지 않았음에도 불구하고 roomCount의 타입은 옵셔널임을 알 수 있다. 그 이유는 zehye.residence?.numberOfRoomes가 nil을 반환할 수도 있고 아닐수도 있기 때문이다.
그러면 도대체 !를 왜 쓰는 것일까? 일단 우선적으로 !는 최대한 적게 사용하는 것이 좋다. 그러나 유용하게 쓸 수 있는 구석은 있다.
!는 언제 사용할까?
때로는 프로그램의 구조상 옵셔널 값을 먼저 설정한 후 그 값이 항상 있는 것이 명백한 경우가 있다.
즉 변수에 값이 있다는 것을 확신할 수 있는 경우를 의미한다. 그런때에 우리가 옵셔널 바인딩과 체이닝을 사용해 조건문을 사용할 이유가 있을까? 없다고 생각한다! 이런 종류의 옵셔널을 implicitly unwrapped optionals이라고 정의한다. 직역을 하자면 암시적으로 언랩핑된 옵셔널? 이라고 할 수 있을 것이다.
이는 주로 클래스 초기화 과정에서 사용된다고 한다.
class ViewContrller: UIViewController {
@IBOutlet weak var leftButton: UIButton!
}
이렇게 IBOutlet같은 변수는 연결했다는 것을 확실히 할수 있기 때문에 !를 붙힐 수 있는 것이다. 근데 이렇게 IBOutlet을 선언했음에도 이 버튼을 사용할수도 안사용할수도 있을 텐데 그럴때는 ?를 사용하면 된다. 만약 ? 혹은 ! 둘다 붙이지 않는다면 에러가 뜨게 될 것이다 > UIButton타입은 non-optional일 수 없는 프로퍼티라는 의미의 에러일 것이다.
지금까지의 실습에서 나는 무조건 IBOutlet 뒤에는 무조건 !를 썼지만 사실 이런 사용법도 좋은 방법이 아니라고 한다. 무조건적으로 !의 사용은 줄이는 것이 좋다고 한다. 지금에야 확실히 이 버튼을 사용한다는 확신이 있지만 연결이 되지 않은 경우도 생길 수 있기 때문이다. 즉 만약을 대비하여 UIButton?을 사용하는 것이 훨씬 좋은 방법이라고 한다! 앞으로의 코드는 ?를 사용하도록 해야지
즉 정리하면 !는 옵셔널 바인딩, 체이닝으로 매번 값을 추출하기 귀찮거나 로직상으로 nil이 할당되지 않을 것 같다는 확신이 들때 사용하면 될 것같다.
옵셔널은 왜 사용하는 것일까?
swift는 일반적으로 아래와 같은 언어이다
- Safe
- Fast
- Expressive
옵셔널을 통해 swift의 안정성을 제공한다.
그리고 더 나아가 프로그래머들간의 원활한 커뮤니케이션을 위해 사용한다고 생각한다.
코드만으로도 변수들이 어떤 타입을 가질지 명확하게 보여주며 함수의 파라미터 부분만 봐도 여기에 어떠한 값들이 들어올 수 있는지 추측이 가능하기 때문이다.