티스토리 뷰

Frontend

Swift에서 String String? String! 의 차이

지마켓 강수진 2020. 11. 4. 13:42

안녕하세요! BXP POD에서 iOS 개발자로 일하고 있는 강수진입니다. 🙋🏻‍♀️

며칠 전 저희 슬랙 방에서 String, String?, String! 에 대한 팝업 퀴즈가 있었습니다.

"흠..다 비슷하게 생겨서 뭐가 뭔지.."라고 생각하시는 분들이 있다면 잘 찾아오셨어요! 이 글은 바로 저렇게 생각하던 제가 깨달음을 얻는 과정을 담고 있기 때문이죠.

그럼 지금부터 저와 함께 Swift에서 String String? String! 의 차이에 대해 알아보러 가실까요?

 

 

퀴즈가 쏘아 올린 작은 공.. 🏐

 

평화로운 어느 오후 iOS 워킹 그룹에 슬랙이 울렸습니다.

 

 

여러분도 한번 생각해보세요! 3..2..1..!

어떤 생각이 떠올랐나요? 우선 제 동료분들의 대답을 살펴볼게요.

 

저의 대답은 이거였습니다.

 

여러분은 저 차이를 아셨나요? 자신 있게 "네!" 라고 대답하신 분들은 뒤로 가기를 누르셔도 좋습니다. 그게 아니라면 반가워요 저의 동지~! 지금부터 알아가면 되죠~ 함께 깨달음의 여정을 떠나보도록 합시다.

 

깨달음의 여정 🚗

 

1. nil이 null이 아니라구요?

 

스위프트에서 Optional 의 특성은 아래와 같이 정의될 수 있습니다.

  • 값이 있을 수도 있고, 없을 수도 있는 것
  • 타입 뒤에 ? 가 붙는 형태로 표현됨
  • 값이 없을 때를 nil 로 표현할 수 있음
var x: String? = nil

 

"흠..🤔 값이 없을 때라.. 그럼 다른 언어에서 배우는 null과 비슷하잖아? Swift에서는 null을 nil이라고 쓰나 보다~"라는 생각이 들지 않으시나요? 적어도 저는 그랬거든요.

 

하지만 이쯤에서 Optional 쪽의 코드를 봅시다. 좀 더 복잡하긴 하지만 필요한 부분만 떼어왔어요.

 

enum Optional<Wrapped> {
  // 값이 없을 때 `nil`으로 표현됨. 이것이 .none 으로 쓰는 것보다 명시적이기 때문
  case none
  // 값이 존재할 때 `Wrapped`으로 저장됨.
  case some(Wrapped)
}

 

 

여기서 크게 두 가지 사실을 발견할 수 있습니다.

  • Optional은 enum 임
  • Optional 안에는 두가지 case가 있음 (.none & .some(Wrapped))

이 정보들을 기억해두고 case none의 주석에 좀 더 주목해 볼까요?

값이 없을 때 `nil`으로 표현됨. 이것이 .none 으로 쓰는 것보다 명시적이기 때문

 

🤭🤭 nil은 enum Optional의 .none 에 해당하는 거였군요!! 다른 언어의 null 같은 게 아니고 enum의 한 케이스일 뿐이었다니..!

따라서 아래 두 줄은 정확히 같은 의미를 나타낼 것입니다.

 

var x: String? = nil
var x: String? = .none

 

여기까지 잘 따라오셨나요?

 

 

그렇다면 위에서 봤던 var x: String = nil 이 왜 에러 상황인지 x의 입장에서 다시 한번 생각해봅시다.

x 는 "String 받는다고 선언해놨는데 왜 enum 을 넣는 거지?" 하는 어리둥절한 상황입니다.

이런 이해 불가의 상황에서는 컴파일 에러를 내게 됩니다.

 

2. String?과 String! 모두 Optional 이라구요~~

 

위에서 Optional를 설명할 때 나온 두 번째 특성 기억나시나요?

  • 타입 뒤에 ? 가 붙는 형태로 표현됨

네 맞습니다. x: String? 이런 식으로 뒤에 ? 가 붙으면 Optional입니다.

var x: String?
if let x = x { x.append("") }

그런데 때때로 프로그래밍을 하다 보면 Optional인데도 값이 있는 것이 보장될 때가 있었을 거예요. 이런 경우에는 if let 등을 통해 Optional을 처리해줄 필요가 사실 없습니다.

 

“Optional이지만 값 무조건 있어..!! 내가 알아!! 그래서 if let 등의 처리 없이 사용하고 싶은데..🤔”

 

이런 니즈를 충족시키기 위해 나온 것이 바로 Implicitly Unwrapped Optional 입니다. ? 대신 ! 를 붙이는 방식으로 정의된답니다.

var x: String! = "naljin"
x.append("")

 

이런 타입은 주로 클래스 초기화에서 사용되는데, 더 자세한 건 애플 문서를 참고하도록 하고 우리는 다시 String! 에 집중해봅시다. 어떻게 if let 등의 처리 없이 바로 값을 사용할 수 있는 걸까요?

 

자 String! 같이 뒤에 느낌표 붙은 애들을 지칭하는 이름이 뭐였죠?

Implicitly Unwrapped Optional

뒤에 Optional이 보이시나요?! 맞아요 String?, String! 둘 다 본질은 Optional 입니다! 다만 String! 은 컴파일 단에서 자동으로 case some 으로 치환을 해준다는 차이점이 있습니다.

 

😕 : some이 뭔데요.. 갑자기 some이 왜 나와요..

 

이렇게 생각하시는 분들을 위해 Optional 쪽의 코드를 다시 가져왔습니다.

enum Optional<Wrapped> {
  // 값이 없을 때 `nil`으로 표현됨. 이것이 .none 으로 쓰는 것보다 명시적이기 때문
  case none
  // 값이 존재할 때 `Wrapped`으로 저장됨.
  case some(Wrapped)
}

 

아까는 .none을 살펴봤다면 이번엔 .some에 집중해봅시다.

옵셔널을 “값이 있을 수도 있고, 없을 수도 있는 것”으로 정의 내릴 때 “값이 없을 때”가 .none 이었죠? 그럼 “값이 있을 때”가 바로 .some이 되겠네요!

String!Optional이긴 하지만, 컴파일 단에서 Optional.some으로 치환해주기 때문에 if let 등의 처리 없이 바로 값을 사용할 수 있다 정도로 이해할 수 있습니다.

실제 LLDB의 po 명령어를 통해 optional 값을 출력해보면 이 some 을 확인할 수도 있답니다.

자자 “String! 은 Implicitly Unwrapped Optional 이다”를 다시 한번 새기고 위의 그림을 가져와 볼게요.

var x: String! = nil 이 왜 컴파일 OK인지 이해가 가시나요?

바로 String! 의 본질은 옵셔널이고, nil (aka .none)은 옵셔널 case이기 때문에 문제가 없는 것입니다! 다만 컴파일때 .some(String)으로 치환해서 무조건 값이 있는 것처럼 사용해 놓고 정작 런타임때 값이 nil이면 "난 믿었을 뿐인데.. 당했다.."하고 런타임 에러가 뜨게 됩니다.

 

마무리 😎

 

처음의 퀴즈를 다시 가져와보았습니다.

 

 

여러분도 이제 답변을 할 수 있게 되었을까요? 그렇다면 좋겠네요!

지금까지의 내용을 간단히 정리하며 글을 마치겠습니다~! 감사합니다 :)

 

String

  • String

String?

  • Optional
  • 따라서 .none도, .some(String)도 될 수 있음
  • 컴파일 단에서 .none인지 .some(String) 인지 알 수 없음
  • 그래서 str?.append 처럼 써야 함

String!

  • Optional
  • 따라서 .none도, .some(String)도 될 수 있음
  • 하지만 컴파일 단에서 무조건 .some(String) 으로 만듦
  • 그래서 str?.append가 아니라 str.append 처럼 쓸 수 있음
댓글