[Swift] Optional 옵셔널을 뿌셔보자...👊

문다연·2022년 1월 18일
0

ios.moon

목록 보기
13/26
post-thumbnail

➰ 들어가며

이전에 문법을 제대로 알지 못하는 상태로 무작정 프로젝트를 진행하면서 간단한 변수선언에서도 항상 어려움을 겪었다. 그럴때마다 Xcode에서 띄워주는 경고창이나 구글링을 해보면 옵셔널에 대한 언급이 정말 많았는데, 이제는 이 옵셔널에 대해 정확히 공부해서 정리할 때가 되었다 🎸

본 글은 inflearn: iOS 개발을 위한 Swift5 완벽 가이드를 수강하며 정리한 글입니다.


➰ Non-optional 논옵셔널

먼저 non-optional에 대해 먼저 알아보자. non-optional은 항상 값을 가져야 한다는 성질이다.
그렇기 때문에 초기화가 되지 않은 변수나 상수의 값을 읽으려고 하면 compile error가 발생하게 된다.

그렇다면 Swift에서 값이 없다는 것은 어떻게 표현할까?
그냥 0을 값이 없다는 뜻으로 쓰면 안되나싶겠지만 그럼 0이 0 자체의 의미로 이용할 수 없게 된다.
이런 면에서 값이 없다는 것비어있는 값을 구분할 필요가 있다.
문자열의 경우 값이 없다면 nil, 값이 비어있다면 공백 으로 표현한다.

Objective-C에서는 nil이 가리키는 값이 없는 포인터를 의미했지만, Swift에서는 포인터가 아니다!
따라서 Swift에서는 참조형이 아닌 값 형식으로 nil을 사용할 수 있다.

그런데 이전처럼 그냥 변수에 nil로 값을 주게 되면 에러가 발생한다. str이라는 이름의 변수는 논옵셔널이기 때문이다. 값이 없다는 뜻의 nil을 값으로 주고싶다면 어떻게 해야할까? 옵셔널을 공부하면 그 답을 찾을 수 있다.


➰ Optional 옵셔널

아래 그림의 왼쪽 형태처럼 아무 기호없이 타입을 선언한 것은 Non-optional이다.

하지만 스위프트에서는 값이 없음, 즉, 값이 nil일수도 있는 변수 또는 상수의 경우에는 반드시 자료형을 Optional로 지정해야 한다. 초기값을 nil로 지정할 때는 형식추론이 안되므로 type annotation 또한 반드시 필요!

String? Int? 어떻게 읽을까?

읽을때는 스트링 물음표가 아니라 Optional String, 옵셔널 스트링으로 표현한다.

let a: Int? = nil
let b = a 
/*
Optional Int인 변수를 값으로 가지면 
형식 추론을 통해 b의 자료형도 Optional Int가 된다.
*/

하지만 옵셔널 자료형의 변수 또는 상수를 출력해보면 다음 사진에서 보이는 문제가 있다.

옵셔널 자료형의 값을 출력해보면 값만이 아닌 옵셔널에 감싸진 형태로 출력되는 것을 볼 수 있다. 이는 옵셔널이 특별한 과정을 통해 값을 저장하기 때문인데, 그에 대한 내부구조는 다음 강의를 통해 공부해보자.
일단은 이 옵셔널을 벗기는 방법을 알아보자!


➰ Unwrapping 언래핑

(1) Forced Unwrapping 강제추출

syntax: [OptionalExpression] !

옵셔널 표현식 뒤에 느낌표! 를 붙여 강제로 옵셔널을 벗기는 방법이다.

위 코드에서 num의 선언은 다음과 같다.

var num: Int? = nil

Optional Int인 num을 값으로 받은 before의 값으로 넣으면,
before의 자료형은 num의 자료형인 Optional Int이다.

Optional Int인 num을 강제 unwrapping한 값을 받은 after의 자료형은 Non-optional Int이다.

만약 강제추출하려는 옵셔널 표현식의 값이 nil이라면 에러가 발생하게 된다.
따라서 반드시 값이 nil인지 아닌지를 판별하는 과정을 거쳐야 한다.

if expr != nil {
	print(num!)
} else {
	print("empty")
}

실제 프로그래밍에서 강제추출은 웬만하면 사용하지 않는 것이 좋다.
unwrapping한 값을 저장하면 논옵셔널 타입으로 결과가 리턴된다.

(2) Optional Binding

앞서 변수 또는 상수가 nil일 때 강제추출하여 오류가 발생하는 경우를 방지하기 위해 if문으로 값이 nil인지 확인하는 과정을 거쳤다. 하지만 실제로는 이 방식으로 코드를 작성하는 경우는 거의 없고 옵셔널 바인딩을 이용한다.

// syntax

if let [name]: [Type] = [OptionalExpression] {
	[statements]
}

while let [name]: [Type] = [OptionalExpression] {
	[statements]
}

guard let [name]: [Type] = [OptionalExpression] else {
	[statements]
}

if, while, guard문에서 이용한다. 괄호 이전까지의 코드 if(또는 while, guard) let [name]: [Type] = [OptionalExpression]Binding 바인딩이라고 하자.
바인딩에서는 옵셔널 표현식을 평가한다. 값이 리턴이 되면 unwrapping된 값이 [name]이라는 변수 또는 상수에 저장되어 리턴되고 코드를 실행한다.
guard에서는 else가 아닌 else 블록 다음의 코드를 실행한다.

옵셔널 표현식을 평가했는데 nil이 리턴되었다면 바인딩이 실패한 것! 이때는 다음문장으로 제어를 넘기게 되고 guard에선 else문의 코드를 실행한다.

바인딩에서의 타입 annotation은 형식추론이 가능하므로 보통 생략한다.

if let n = num { // 바인딩 과정에서 이미 언래핑된 값이므로 또 다시 언래핑할 수 없다.
	print(n)
    n // 언래핑된 값이므로 논옵셔널 타입이다.
} else {
	print("empty")
}

추가로 바인딩에 사용하는 상수 또는 변수의 이름이 다른 스코프에 있는 것과 동일하더라도 그 둘이 같은 것을 가리키지는 않다! 바인딩, guard문에서는 이에 대한 혼동에 유의하자...!!

(3) IUO: Implicitly Unwrapped Optionals

옵셔널 묵시적 추출. 자동추출, 암시추출의 옵셔널 형태이다. 이쯤부터 잔뜩 헷갈린다 ...
Type annotation시에 [Type]!형태로 선언할 수 있다.

위 코드에서 a는 형식추론을 사용한다. 그러나 IUO는 형식추론을 사용할 때 자동으로 추출되지 않으므로 a의 자료형은 Optional Int이다.

let b: Int = num // Non-optional Int (Int)

하지만 numNon-optional Int로 바꾸면 에러가 발생하고 b에서 강제추출이나 옵셔널 바인딩 처리를 해주어야 한다.

numnil이 저장되어 있는 상태에서 b의 값으로 저장하면 오류가 발생한다. IUO는 값을 확인하지는 않으므로 유의해야 한다.

IUO를 사용하는 상황은 IBOutlet이나 API에서 IUO를 리턴하는 두 가지 경우이다. IUO를 굳이 사용하지 않는 것을 권장하고, 대신에 옵셔널 바인딩이나 옵셔널을 사용하는 것이 가장 좋다!


➰ Nil-Coalescing Operator

Coalescing은 합치다라는 뜻으로, Nil-Coalescing Operator는 nil 병합 연산이다.
앞서 옵셔널 바인딩을 통해 언래핑하는 방법을 확인해봤다. 예시 코드를 한 번 봐보자...

옵셔널 바인딩 방식으로 input에 대한 바인딩이 성공하여 값이 리턴되면 msg = "Hello, " + inputName이 실행되고, 실패하여 nil이 리턴되면 msg = "Hello, Stranger"가 실행될 것이다.

조건 연산자를 사용하여 더 단순하게 구현할 수 있다.

Nil-Coalescing을 이용하면 값이 저장되어있는지 확인하는 코드와 값을 추출하는 코드를 따로 쓸 필요가 없다.


➰ Optional Chaining

옵셔널을 연달아 호출하기. Optional Chainig의 가장 중요한 점 두가지는 다음과 같다.

  1. Optional Chaining의 결과는 항상 Optional이다.
  2. Optional의 표현식 중 하나라도 nil을 리턴한다면 그 이후에 이어지는 표현식은 평가하지않고 바로 nil을 리턴한다.

Optional Chaining을 쓰다보면 언제 어디서 optional을 붙여줘야 하는지 헷갈리기 쉽다. Optional Chaining에 대한 설명과 예시는 Github: Optional Chainging로 대신 확인해보자 💽


➰ Optional Pattern

Github: 예제 소스코드

옵셔널에는 두가지 표현 문법이 있다.

// 1. 단축 문법
let a: Int? = 0

// 2. 정석 문법. 이론적으로만 알아도 됨.
let b: Optional<Int> = 0

이때 옵셔널의 값을 두가지 방법으로 평가해보자.

if a == nil { ... }
if a == .none { ... }

위 두 코드는 같은 의미의 코드이다. Optional은 열거형으로 구현되어 있고, none 또는 some 케이스가 있다. nil이라면 옵셔널에서는 .none, 값이 있다면 .some(Int)로 표현된다.

→ enumeration case 패턴과 optional 패턴을 조합하여 아래와 같이 활용할 수 있다.

if let x = a {
   print(x)
}

위 코드를 enumeration case pattern으로 작성해보자

if case .some(let x) = a {
   print(x)
}

enumeration case pattern으로 작성할 수 있다면 optional pattern도 적용할 수 있다.

if case let x? = a {
   print(x)
}

Iterating Example

let list: [Int?] = [0, nil, nil, 3, nil, 5]

for item in list {
  guard let x = item else { continue } // nil이 들어오면 continue
  print(x)
}

// 결과는 동일하지만 guard문을 작성하지 않아도 되므로 간단해진다.
for case let x? in list {
  print(x)
}
profile
ios-moon.tistory.com 이전했어요 🚛

0개의 댓글