티스토리 뷰

Swift

swift3 ) Optional 개념 정리

Zedd0202 2017. 1. 25. 21:19
반응형

안녕하세요 :)

오늘은 Optional이 뭔지에 대해 정리해볼려고해요.

이것도 yagom님이 BoostCamp강의에서 설명을 엄청 잘해주셨답니다 :)

yagom님 강의를 토대로 추가할 부분은 추가해가면서 정리해볼게요  XD



먼저 Optional이 뭔지 알아야겠죠?

optional의 뜻은  "선택적인"이라고 직역할 수 있겠네요.

평소 우리 생활에서 "옵션"이라고 그러면 뭐 있어도 되고 없어도 되는 그런 느낌이죠?

swift에서도 마찬가지 입니다 :)




그러면 swift에서 이 "선택적인"것이 왜 필요할까요? 

코딩을 하면서 어떠한 변수에 값이 있을 수도 없을 수도있는 경우를 위해서 입니다.

swift공부를 하시거나 iOS개발을 하시면서 

? / ! 기호를 많이 보셨을거에요. 

이것이 바로 optional 기호인데요, 

swift에서는 ? / ! 기호를 타입 어노테이션 옆에 붙여줘야 한답니다. 

예제를 한번 볼게요.



var test : Int

test = nil


자, 잘 작동하나요? 아쉽게도 아닙니다. Xcode는


라는 에러를 내게되죠.

왜일까요? 

오류를 해석하면 nil은 Int형 타입에 할당 될 수 없다고 하죠?

( nil은 '값의 부재'라고 생각하시면 될 것 같아요 :) )



swift에서는 기본적으로 변수를 선언할 때 

non-optional인 값을 주어야 합니다.

즉 어떠한 '값'을 변수에게 주어야 한다는거죠.

swift에서는 Int형으로 선언한 변수는 무조건 정수타입이 들어가야합니다. 



하지만 nil이 들어갔죠? 

Xcode는 "이건 정수타입이 아니야!" 하며 에러를 내게 되는거죠. 

애초에 Int타입에 Int타입이 아닌 것을 넣으니까

 저 Int라는 메모리공간이 초기화 될 수 없는 것입니다. 



하지만 코딩을 하다보면 어떠한 변수에 값이 안들어 갈 수도 있겠죠?

한마디로 우리는 변수안에 값이 확실히있다는 것을 

보장 할 수 없으면 Optional을 쓰면 된답니다.


이제 이 오류를 없애볼게요. 


var test : Int?

test = nil


자, Int옆에 ?만 붙혔을 뿐인데 오류가 사라졌죠?

이번엔 ?대신 !를 붙혀봅시다. 

그래도 오류가 나지 않죠

바로 optional기호를 붙혔기 때문이에요.

이 변수안에는 값이 있을 수도, 없을 수도 있다는 것을 명시해준거죠.

optional변수는 초기화 하지 않으면 nil로 초기화가 자동으로 된답니다 XD




optional변수에서는 값을 어떤식으로 가져올까요?

방법은 여러가지가 있습니다.

차근차근히 설명해드릴게요.



일단 이해가 가기 쉽게 예를 들어 설명해 드릴게요.

상자를 하나 떠올려보세요 :)


우리가 그동안 선언해 온 변수는 

왼쪽처럼 상자안에 들어가 있지 않다고 생각해보세요.

그리고 optional변수는 바로 저렇게 

상자 안에 값이 들어가있다고 생각해보죠.


상자안에는 값이 있을 수도, 없을 수도 있어요.

바로 이렇게요! 

 만약 변수가 optional로 정의가 되었으면, 

일단 먼저 상자 하나를 만드는 거에요.


자 이제 ?와 !의 차이를 상자를 통해 알려드릴게요.


1. ?


옵셔널로 변수가 선언이 되어있으니 Xcode는 먼저 상자하나를 만들겠죠?

만약 거기에 ?가 붙으면 Xcode는 상자에 '노크'를 하게 됩니다.


"안에 누구 있니?ㅎㅎ"

만약 상자 안에 값이 있으면 

변수는 "응 나 30 가지고있어" 하고 왼쪽처럼 30을 얻게 되겠죠. 

하지만 상자 안에 열었는데 아무것도 없으면?

오른쪽처럼 nil을 반환해주게 됩니다. 

(오른쪽 상자 안에서 nil이 나오도록 그린 이유는 

nil이 상자 안에있어도 메모리를 차지하게 되기 때문입니다ㅎㅎ)

예제 코드를 하나 같이 만들어 볼까요?


 var someValue : Int? = 30

 var Value = someValue


이때 Value의 타입은 무엇이 될까요? 

someValue의 Int뒤에 ?가 붙었죠?

우리는 이제 Optional이 뭔지 압니다.


"someValue 타입 어노테이션에 ?가 붙었네?"

"someValue에는 정수가 들어 갈 수도 있겠지만

nil이 들어갈 수도 있겠구나"


근데 Value라는 애한테 someValue를 넣어줬네?

여기서 문제입니다.

Value의 타입은 Optional일까요, 아닐까요?

정답은 Value는 Optional타입이 되게 됩니다.

 var Value = someValue 의 의미는


Value는 optional 타입이고 

Int데이터형을 가질 수 있는 변수야

(=Int형 값을 가질 수도, 안가질 수도 있어)


가 되겠네요..!


하지만, Value에 타입을 확실하게 명시해줘볼까요? 


var someValue :Int? = 30

var Value : Int = someValue


이렇게요. 

오류가 뜨시나요?


우리는 지금 "Value라는 애는 Int형 데이터 밖에 못가져!"

라고 명시해준겁니다.

거기에 Optional타입을 가지는(nil이 들어 갈 수도 있는) someValue를 넣어주었으니 당연히 Value는 someValue를 받아들이지 못합니다.

아직 노크를 안한상태라고 할 수 있겠네요.



우리는 여기서 한가지 사실을 알게 되네요.

Int와 Int?는 다른 타입이구나


기억하세요.

Optional과 Optional이 아닌 것은 

다른타입입니다!






2. !


그럼 !는 뭘까요? 

바로 이런느낌입니다. 이것을 강제 언래핑(Unwrapping)이라고 부르는데요,

노크따위 하지 않습니다. 

상자안에 값이 있든 말든 그냥 일단 깨부시고 값을 가져오겠다는 것입니다.

깨부셨더니 운좋게 값이 있을 수도있겠고, 또한 없을 수도 있겠죠.


위에있던 예제를 한번 다시 볼까요?


var someValue :Int? = 30

var Value : Int = someValue


이렇게 하면 오류가 났었죠? 



var someValue :Int? = 30

var Value : Int = someValue!


someValue뒤에 !를 붙혀보겠습니다. 


자, 오류가 없어지셨나요?

왜 오류가 없어졌을까요?


왜냐하면 강제로 상자를 부시고 값을 꺼냈으니까요!



someValue안에는 30이라는 값을 우리가 줬었죠?

깨부셨더니 운좋게 Int형 데이터 30이 들어있었던거죠!!

그래서 Optional이 아닌 Int형의 Value타입에 잘 들어갈 수 있었던거죠.


상자를 깨부시고, 값을 꺼내고, 

그 값(정수)을 Value에 넣어준 코드이니

Value입장에서는 아무 문제가 없죠.


Value는 Int형 데이터만 받을 수 있는데

원하는데로 Int형 데이터(30)을 주었으니까요.

이해가 가시나요?

 



그럼 지금쯤 이런 궁금증이 드실 수 있겠네요.

상자를 깨부셨는데 안에 값이 없으면..?


바로


var someValue : Int? = nil

var Value : Int = someValue!


이렇게요.

(someValue는 Optional타입이니 당연히 nil이 들어갈 수 있습니다.)

자, 어떤 결과가 나오실 것 같으세요?



음..일단 컴파일러가 오류는 띄우지 않네요?

게다가 Build Success까지..!

되나보다 ~


하신 분들 계신가요?



ㅎㅎ곧 이런 오류를 보실 겁니다.



당연한 결과겠죠?

 nil이라는 값을 접근하려고 하니 당연히 오류가 나게 되는거죠. 

컴파일러는 잡지 못하기때문에 겉으로 보기에는 오류가 없어보이지만

이런경우 100%런타임 에러가 발생하게 됩니다. 

C++에서의 NULL포인트 exception을 생각하시면 될 것 같아요.


그래서!!!!!!!!

 !사용은 조심하셔야해요 :)

스위프트 언어 가이드에 보시면


느낌표(!)를 사용하여 값이 존재하지 않는 옵셔널 값에 접근하려 시도하면 

런타임 에러가 발생합니다. 

느낌표를 사용하여 강제 언랩핑을 하기 전에는 

항상 옵셔널 값이 nil이 아니라는 것을 확실히 해야 합니다.


라고 그러네요. 

nil이 아니라는 것이 확실하지도 않은 상태에서 !를 남용하여 쓰게 되면 

오류가 날 가능성이 높아지겠죠?


또한 !도 Optional이기 때문에 초기화 할 때 값을 요구하지 않습니다. 

초기화를 안해주면 ?와 마찬가지로 nil이 들어가겠죠?





자, 다음으로

그러면 우리는 어떻게 Optional변수의 값을 가져올 수 있을까요?

첫번째로 옵셔널 바인딩(Optional Binding)과 

옵셔널 체이닝(Optional Chaining) 이 있습니다.


그리고 강제 언랩핑(Forced Unwrapping)이 있는데.. 

단어에서 느낌이 오시나요? 

바로 위에서 설명한 !를 사용한 방법입니다 :)

상자를 깨부시고 값을 얻어오는 방법이죠?

이 부분은 옵셔널 바인딩과 체이닝을 설명드린 뒤 

부가설명을 해드릴거에요 :)




먼저

옵셔널 바인딩과 옵셔널 체이닝을 설명해드릴게요.



1. 옵셔널 바인딩(Optional Binding)


옵셔널 바인딩은 주로 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에 넣어주고

조건문을 실행해"

라는게 되겠네요.



하지만 myName상자에는 nil이 있죠?

(var myName: String? =  nil)

그러므로 조건문은 실행하지 않게됩니다.

값이 있을때만 값이 바인딩 되기 때문이죠.

( 이때문에 옵셔널 바인딩이라고 부르는 것 같습니다 :) )



자 그래서, 우리는 if let(또는 if var)구문을 통해 

nil일 경우와 아닌경우를 안전하게 대비할 수 있습니다. 


여러개 체크도 당연히 가능하겠죠?

간단하게 예제를 들자면,



var height : Int? = 170

if let value = height {

    if value >= 160 {

        print("wow")

    }

}



var height : Int? = 170

if let value = height, value >= 160{

     print("wow")



두 코드 결과 : wow 




위와 아래의 코드는 결과적으로 똑같은 기능을 하는 코드인거,

아시겠나요?

저렇게 ,를 통해서 &&의 효과도 줄 수 있답니다. 



 

2. 옵셔널 체이닝 (Optional Chaining)


이름에서도 알 수 있듯이 뭔가 체인? 뭔가 이어진다는 느낌이네요.

옵셔널 체이닝은 하위 property optional 값이 있는지 연속적으로 확인하면서, 중간에 하나라도 nil 발견된다면 nil 반환되는 형식입니다

글로 말하니 잘 모르시겠죠?

에제를 하나 들어볼까요?

스위프트 언어가이드 - Optional Chaining부분에 나와있는 예제입니다. 




class Person {

    var residence: Residence

//residence라는 변수가 Residence 클래스를 상속받고 있네요

하지만 Optional기호 ? 같이 주었습니다

밑에서 Person타입의 인스턴스가 만들어지면 residence변수의 초기값은 nil 되겠네요.

}

class Residence {

    var numberOfRooms = 1

}



let zedd = Person()

//여기서 Person타입의 인스턴스가 zedd 만들어졌네요

zedd 프로퍼티로 residence 있겠죠?

(Person클래스의 멤버변수이기 때문

하지만 residence변수는 Residence클래스를 

옵셔널 타입으로 상속받고 있기 때문에 

residence에는 값이 있을수도, 또는 없을수도 있겠네요

위에서 말씀드렸죠

옵셔널 타입은 따로 초기화를 하지 않으면 nil 초기화가 된답니다

그러면 현재 zedd residence 값은 nil이겠네요!


        

if let roomCount = zedd.residence?.numberOfRooms {

    print("zedd's residence has \(roomCount) room(s).")

} else {

   print("Unable to retrieve the number of rooms.")

}


결과 : Unable to retrieve the number of rooms.



자, 코드의 어느부분이 옵셔널 체이닝 방법을 쓴 것 같으세요?
네. 맞습니다. 


let roomCount = zedd.residence?.numberOfRooms


바로 이부분이죠.

프로퍼티를 통해 계속 체인처럼 이어져있죠?

zedd의 residence가 nil이 아니라면, 다음으로 넘어가 

residence의 numberOfRooms를 또 확인하죠.

여기서는 zedd의 residence가 nil이기 때문에 (위의 주석 설명)

else문을 수행하게 된거죠



위 코드가 if문의 조건을 수행하게 하려면, 


 zedd.residence = Residence()


라는 코드를 추가해주면 될 것 같네요 :)

그러면 nil이 였던 residence의 값이 더이상 nil이 아니게 되고,

zedd's residence has 1 room(s).

라는 결과를 주겠네요 :)





그러면 여기서 궁금한 점이 있으실 것 같아요.

let roomCount = zedd.residence?.numberOfRooms

"이부분에서 왜 residence뒤에 ?표시가 붙었지?"
이유는 residence가 nil을 반환할 수도 있고 아닐 수도 있기 때문입니다.


하나만 기억하세요.

 하위 프로퍼티 Optional 값이 있는지 연속적으로 확인하면서
중간에 하나라도 nil 발견된다면 nil 반환하는 것이 옵셔널 체이닝 방식입니다.



그럼 여기서 저희는 한가지 사실을 알 수 있습니다.
roomCount의 타입은 옵셔널일까요 아닐까요?

 zedd.residence?.numberOfRooms
가 nil을 반환할 수도, 아닐 수도 있으니
당연히 옵셔널로 자동으로 지정되겠죠?
한번 확인해 볼까요?


보이시나요? roomCount의 타입은 현재 Int?입니다.

저희가 따로 Optional로 지정을 하지 않았어도요:)



그리고


if let roomCount = zedd.residence?.numberOfRooms


이 부분! 뭔가 익숙하지 않으시나요?

네 맞습니다. 위에서 배운 옵셔널 바인딩의 모습같죠?

if let구문을 쓴 거 보니까요 :)

이렇게 옵셔널 체이닝은 옵셔널 바인딩과 종종 같이 쓰이곤 한답니다.



어려워보이지만 간단한 예제이니 꼭 따라해보시는 게 좋을 것 같아요 :)



옵셔널 바인딩과 옵셔널 체이닝을 설명드린 후에

!를 사용하는 강제 언래핑(Forced Unwrapping)에 보충설명을 해드릴려고

위에서 말하지 않은 것이 몇가지 있습니다 XD


상자를 깨부시는 강제 언래핑(Forced Unwrapping)!

먼저 말씀드리지만, 사용을 최대한 줄이는 것이 좋습니다.

하지만 유용하게 쓸데가 있으니 만들어 놨겠죠?




!는 언제 쓰면 좋을까요?


때로는 프로그램의 구조상 Optional값을 먼저 설정한 후, 

그 값이 항상 있는 것이 명백한 경우가 있겠죠? 


간단히 말해서,

 우리는 이 변수에 값이 있다는 것을 확신할 수 있는 경우죠.

이럴경우에, 

우리는 위에서 했던 옵셔널 바인딩과 옵셔널 체이닝을 이용해

"여기에 값이 있으면 이거 해주고, 없으면 저거해줘"

라고 해줄 필요가 있을까요? 

우리는 여기에 값이 있다는 것을 확신할 수 있기 때문에

굳이 해줄 필요가 없겠죠? 


이런종류의 Optional을

implicitly unwrapped optionals이라고 정의합니다 :)

직역을 하자면, 무조건적으로 언랩핑된 옵셔널?암시적으로 언랩핑된 옵셔널?

이라고 할 수 있겠네요.


이 implicitly unwrapped optionals은 주로 클래스 초기화 과정에서 사용된다고 해요 :)

!를 사용하려면 클래스 인스턴스의 모든 값이 세팅되어 있어야 겠죠? 



그리고 생각해보면 우리는 !를 정말 많이 쓰고 있었던 것. 혹시 아시나요?



저기 보이시나요? 

 UIButton!


이렇게 IBOutlet같은 변수는 연결했다는 것을 확실히 할 수 있기 때문에 !를 붙힐 수 있는 것입니다.

하지만 IBOutlet을 선언은 해놓고 연결을 안할 수도 있겠죠? 

그럴땐 ?를 붙혀주면 된답니다.


만약 아무것도 (?/!) 안붙히면 어떻게 될까요? 




이렇게 오류가 나죠?


UIButton타입은 non-Optional일 수 없는 프로퍼티라네요 :) 




"@IBOutlet에서는 !를 쓰면 되는구나~"

라고 생각 할 수 있겠지만

!사용은 최대한 줄이는 것이 좋습니다.

지금이야 확실히 연결했다는 것을 알 수 있겠지만, 연결이 되지 않았을 경우도 있겠죠?

그럴경우를 대비에  UIButton! 보다는  UIButton?이 좀 더 안전하겠네요 :)



정리하자면 !는 

옵셔널 바인딩, 체이닝으로 매번 값을 추출하기 귀찮거나 로직상으로 nil이 할당되지 않을 것 같다는 확신이 들 때 사용하면 좋겠네요. 



여기까지 오시느라 수고하셨어요 :)

그럼 여기서 문제입니다.

Optional이 이제 뭔지는 아시겠죠?

그럼 이 Optional을 왜 쓸까요?

"nil이 들어갈 수도 있으니까?"

라는 대답이 많을 것 같아요. 위에서 계속 nil로 설명을 드렸으니까요.

결과적으로 맞는 대답인 것 같아요.

설명을 드리자면,

Swift는 

- Safe

- Fast

- Expressive

한 언어입니다. 


어떻게 "안전하다"는 것을 알 수 있을까요?

감이 오시나요? 위에서 말했던 Optional을 통해 

Swift의 안전성을 제공합니다. 


또한 프로그래머간의 원활한 커뮤니케이션을 위해 사용합니다. 

제가 함수 하나를 작성해 볼게요. 



func optionalTest (name : String?){

    print(name)

}


func optionalErrorTest (name: String){

    print(name)

}


 optionalTest(name: nil)

 optionalErrorTest(name: nil)//오류 





자, 이 코드를 다른 사람이 보면 어떻게 생각할까요?
 optionalTest 함수의 파라미터로 
String이 들어갈 수도, nil이 들어갈 수도 있겠구나!
하지만 optionalErrorTest함수에는 무조건 String 데이터만 들어가야 하는구나!

이렇게 함수의 파라미터 부분만 보더라도 여기에 어떠한 값들이 들어올 수 있는지
추측할 수 있다는 겁니다. 

"프로그래머간의 원활한 커뮤니케이션"
이제 무슨 뜻인지 알 것 같나요?


정말 길고 긴 Optional정리가 끝났습니다.
저도 글쓰면서 정말 많은 공부가 되었네요.

도움이 되었으면 좋겠어요🙏






 



반응형