Roel Notebook

[Swift] Optional

by Roel Downey
728x90
반응형

공부하고 정리할 내용

- 옵셔널 (Optional)

- 옵셔널 추출 (Optional Unwrapping)

- 옵셔널 체이닝 (Optional chaining)과 빠른 종료 (Early Exit)

- 옵셔널 패턴 (Optional Pattern)

 

옵셔널 (Optional)

옵셔널은 스위프트의 특징 중 하나인 안정성을 문법으로 담보하는 기능이다.

옵셔널은 단어 뜻 그대로 '선택적인' , 즉 값이 있을 수도 있고 없을 수도 있음을 나타내는 표현이다.

이는 ' 변수나 상수 등에 꼭 값이 있다는 것을 보장할 수 없다. 즉, 변수 또는 상수의 값이 nil 일수도 있다는 것을 의미 

 

예를 들어보자.

let number = 1

이 코드를 실행하면 메모리에 1이 저장되고 형식은 Int로 추론된다. 여기에서 초깃값을 저장하지 않으면 형식을 추론할 수 없어서 컴파일 에러가 발생한다. 

let number: Int

 

이때는 상수 또는 변수의 형식을 명시적으로 선언해야 한다. 이 코드에서는 number 상수에는 초깃값이 저장되어 있지 않다. 이 상태에서 값을 읽으면 에러가 발생한다. 변수와 상수를 공부할 때 '변수와 상수는 값을 읽기 전에 반드시 초기화해야 한다' 고 공부했다. 항상 값을 가져야 하는 Non-Optional Type으로 선언되어 있기 때문이다. 

 

Optional Type은 값을 가지지 않아도 되는 형식이다.

let optionalNumber: Int? = nil

위의 코드는 optionalNumber 상수를 선언하고 nil을 저장하고 있다. nil은 값이 없다는 것을 나타내는 특별한 키워드이다.

nil을 저장하는 것은 값을 저장하지 않는 것과 같다.

 

Int 타입의 변수에 0을 할당 했다면 값이 없다는 의미가 아니라 0의 값이다.

String 타입의 변수에 "" 을 할당했다면 값이 없다는 의미가 아니라 빈 문자열이라는 값이다.

 

옵셔널 변수의 선언 및 할당

선언은 두 가지 방법이 있다.

1. var optionalName: Type?Value

2. var optionalName: Optional<Type?> = Value

(GrayItalicFont로 작성된 요소는 실제 값으로 바꿔야 하는 요소이다.)

 

첫 번째 방법은 조금 더 편하고 읽기도 쉽다.

두 번째 방법은 명확하게 사용한다.

 

옵셔널은 열거형으로 구현되어 있다.

// 옵셔널 열거형의 정의
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
case none
case some(Wrapped)
public init(_ some: Wrapped)
// .. 중략
}

 

옵셔널은 제네릭이 적용된 열거형이다. 여기서 알아야 할 것은 옵셔널이 값을 갖는 케이스와 그렇지 못한 케이스 두 가지로 정의되어 있다는 것이다. 즉, nil일 때는 none 케이스가 될 것이고, 값이 있는 경우는 some 케이스가 되는데, 연관 값으로 Wrapped에 값이 할당된다. 즉, 값이 옵셔널이라는 열거형의 방패막에 보호되어 래핑 되어 있는 모습이라는 거다.

 

옵셔널 자체가 열거형이기 때문에 옵셔널 변수는 switch 구문을 통해 값이 있고 없음을 확인 할 수 있다.

 

옵셔널 추출 (Optional Unwrapping)

열거형의 Some 케이스로 꼭꼭 숨어있는 옵셔널의 값을 옵셔널이 아닌 값으로 추출하는 옵셔널 추출 방법에 대해 알아보자.

1. 강제 추출 (Forced Unwrapping) 

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

3. 암시적 추출 옵셔널 (Implicitly Unwrapped Optionals)이 있다.

강제 추출은 가장 간단하지만 가장 위험한 방법이므로 넘어가겠다. 

 

옵셔널 바인딩

옵셔널에 값이 있는지 확인할 때 사용한다. 만약 옵셔널에 값이 있다면 옵셔널에서 추출한 값을 일정 블록 안에서 사용할 수 있는 상수나 변수로 할당해서 옵셔널이 아닌 형태로 사용할 수 있도록 해준다. 옵셔널 바인딩은 if 또는 while 구문 등과 결합하여 사용할 수 있다.

 

if let name : Type = OptionalExpression {

        statements

}

(GrayItalicFont로 작성된 요소는 실제 값으로 바꿔야 하는 요소이다.)

while let name : Type = OptionalExpression {

        statements

}

(GrayItalicFont로 작성된 요소는 실제 값으로 바꿔야 하는 요소이다.)

 

guard let name : Type = OptionalExpression {

        statements

}

(GrayItalicFont로 작성된 요소는 실제 값으로 바꿔야 하는 요소이다.)

 

Type은 옵셔널 표현식을 통해 추론할 수 있어서 생략 가능하다. 옵셔널 바인딩에서 초깃값은 항상 옵셔널 표현식이다. 

옵셔널 바인딩은 옵셔널 표현식이 값을 리턴하는 경우에만 추출하기 때문에 강제 추출과 같은 에러는 발생하지 않는다. 그래서 더 안전한 코드를 작성할 수 있다.

 

 

옵셔널 바인딩을 통해 한 번에 여러 옵셔널의 값을 추출할 수도 있다. 쉼표(,)를 사용해 바인딩 할 옵셔널을 나열하면 된다. 단, 바인딩하려는 옵셔널 중 하나라도 값이 없다면 해당 블록 내부의 명령문은 실행되지 않는다.

 

 

암시적 추출 옵셔널

때때로 nil을 할당하고 싶지만, 옵셔널 바인딩으로 매번 값을 추출하기 귀찮거나 로직상 nil 때문에 런타임 오류가 발생하지 않을 것 같다는 확신이 들 때 nil을 할당해줄 수 있는 옵셔널이 아닌 변수나 상수가 있으면 좋을 거다. 이때 사용하는 것이 암시적 추출 옵셔널이다. 옵셔널을 표시하고자 타입 뒤에 물음표(?)를 사용했지만, 암시적 추출 옵셔널을 사용하려면 타입 뒤에 느낌표(!)를 사용해주면 된다.

그래서 나는 안쓸 예정이다. 이것도 강제 추출이랑 다를게 없어서 예제 정리를 안하겠다.

 

옵셔널 체이닝 (Optional chaining)과 빠른 종료 (Early Exit)

옵셔널 체이닝은 두 가지만 기억하면 된다.

1. 옵셔널 체이닝의 결과는 항상 옵셔널이다.

2. 옵셔널 체이닝에 포함된 표현식 중 하나가 nil을 리턴하면 나머지 표현식을 평가하지 않고 바로 nil을 리턴한다.

 

옵셔널 체이닝은 여러 값이 중첩된 형태를 띄어야 제 몫을 발휘한다.

 

옵셔널 체이닝

옵셔널 체이닝은 옵셔널에 속해 있는 nil일지도 모르는 프로퍼티, 메서드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정이다. 옵셔널에 값이 있다면 프로퍼티, 메서드, 서브스크립트 등을 호출할 수 있고, 옵셔널이 nil이라면 프로퍼티, 메서드, 서브스크립트 등은 nil을 반환한다. 즉, 옵셔널을 반복 사용하여 옵셔널이 자전거 체인처럼 서로 꼬리를 물고 있는 모양이기 때문에 옵셔널 체이닝 이라고 부른다. 자전거 체인에서 한 칸이라도 없거나 고장 나면 체인 전체가 동작하지 않듯이 중첩된 옵셔널 중 하나라도 값이 존재하지 않는다면 결과적으로 nil을 반환한다.

옵셔널 체이닝은 프로퍼티나 메서드 또는 서브스크립트를 호출하고 싶은 옵셔널 변수나 상수 뒤에 물음표(?)를 붙여 표현한다. 옵셔널이 nil이 아니라면 정상적으로 호출될 것이고, nil이라면 결괏값으로 nil을 반환할 것이다. 결과적으로 nil이 반환될 가능성이 있으므로 옵셔널 체이닝의 반환된 값은 항상 옵셔널이다.

 

옵셔널 체이닝을 사용하면 위의 코드를 더 간결하게 쓸 수 있다.

let array: [String]? = []

let optionalIsEmptyArray = array?.isEmpty == true
// true

 

빠른 종료(Early Exit)

빠른 종료의 핵심 키워드는 guard 이다. guard 구문은 if 구문과 유사하게 Bool 타입의 값으로 동작하는 기능이다.

코드블록을 종료할 때는 return, break, continue,throw 등의 제어문 전환 명령을 사용한다. 또는 fatalError()와 같은 비반환 함수나 메서드를 호출할 수도 있다.

guard 구문의 한계는 자신을 감싸는 코드 블록, 즉 제어문 전환 명령어를 쓸 수 없는 상황이라면 사용이 불가능하다. 함수나 메서드, 반복문 등 특정 블록 내부에 위치하지 않는다면 사용이 제한 된다.

 

guard Bool타입 값 else {

    예외사항 실행문

    제어문 전환 명령어

(GrayItalicFont로 작성된 요소는 실제 값으로 바꿔야 하는 요소이다.)

}

 

 

Nil-Coalescing Operator

var input: String? = "Roel"
var name = "Hello," + (input != nil ? input! : "Stranger")
print(name)
// Hello,Roel

조건 연산자를 사용해서 작성 할 수 있다. 조건 연산자를 보면 첫 번째 피연산자에서 input 변수에 값이 저장되어 있는지 확인한다. 값이 저장되어 있다면 값을 추출해서 리턴하고 값이 없다면 Stranger를 리턴한다. 

 

Nil-Coalescing 연산자를 사용하면 코드를 더 직관적으로 작성 할 수 있다.

 

Optional_Expr ?? Expr

(GrayItalicFont로 작성된 요소는 실제 값으로 바꿔야 하는 요소이다.)

 

Nil-Coalescing 연산자는 이항 연산자이고 ? 문자 두개로 구성되어 있다. 왼쪽 피연산자는 항상 옵셔널 표현식이다. 오른쪽 피연산자는 일반 표현식이다. 두 피연산자의 형식은 옵셔널을 제외하고 동일해야 한다. 예를 들어 왼쪽 피연산자가 String?이라면 오른쪽 피연산자는 String이 되어야 한다.

 

?? 연산자는 왼쪽 피 연산자에 값이 저장되어 있는지 확인한다. 값이 저장되어 있다면 값을 추출해서 리턴한다. 반대로 값이 없다면 오른쪽 피연산자를 리턴한다. 그래서 오른쪽 피연산자에는 주로 값이 없을 때 사용할 기본값이 온다.

 

var input: String? = nil
var name = "Hello," + (input ?? "Stranger")
print(name)
// Hello,Stranger

옵셔널 패턴 (Optional Pattern)

옵셔널 패턴(Optional Pattern)은 Enumeration Case Pattern의 옵셔널 버전이다. Enumeration Case Pattern을 사용할 수 있는 곳이라면 옵셔널 패턴을 사용할 수 있다.

optionalIdentifier?

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

for case let x? in list {
    print(x)
}

 

[참고 링크]

Apple Developer: [Optional Chaining]

728x90
반응형

'iOS > Swift' 카테고리의 다른 글

[Swift] Enumerations  (0) 2019.07.16
[Swift] Functions  (0) 2019.07.12
[Swift] Collection [ 간단하게 정리하기]  (0) 2019.07.12
[Swift] Array  (0) 2019.07.10
[Swift] Naming Convention  (0) 2019.07.07

블로그의 정보

What doing?

Roel Downey

활동하기