Jerry's Bakery

[iOS] Optional(옵셔널), Optional Binding(옵셔널 바인딩), Optional Chaining(옵셔널 체이닝) 본문

개발/iOS

[iOS] Optional(옵셔널), Optional Binding(옵셔널 바인딩), Optional Chaining(옵셔널 체이닝)

_Jerry 2021. 10. 19. 02:12

안녕하세요 Jerry입니다.

 

Optional과 옵셔널 언래핑 방법에 대해 알아보겠습니다.

 

iOS를 공부하면서 정리하는 것이기 때문에 미흡한 점이 있을 수 있습니다. 부족한 점에 대해 댓글 남겨주시면 감사하겠습니다.

 

이 글은 아래 공식문서를 기반으로 만들어졌습니다.

 

Apple Developer Documentation

 

developer.apple.com

 

Optional(옵셔널)이란?

Optional 은 enum 타입이며 코드는 아래와 같습니다.

enum Optional<Wrapped> {
    case none
    case some(Wrapped)
}

enum에 대한 정의를 알고 싶다면 아래 블로그를 방문해주시면 감사하겠습니다.

 

[iOS] enum과 switch

안녕하세요 Jerry입니다. enum과 switch에 대해 알아보겠습니다. iOS를 공부하면서 정리하는 것이기 때문에 미흡한 점이 있을 수 있습니다. 부족한 점에 대해 댓글 남겨주시면 감사하겠습니다. 이 글

jerry-bakery.tistory.com

 

Optional은 none과 some 두가지 값을 가지는 enum입니다.

 

Optional을 언제 사용하나요?

변수의 값이 때때로 정의되지 않았거나 결정되지 않았을 경우 사용될 수 있습니다. 그럴 때는 이 변수는 Optional enum의 none case일 것입니다. 

 

Optional 사용 방법

Optional이라는 단어를 입력하지 않더라도 선택적인 값을 사용할 때마다 Optional 타입을 사용합니다. Swift의 타입 시스템에서 Optional을 선언한다면 타입 어노테이션은 Optional<Int>일 것입니다. 하지만 var a: Int?로 타입 어노테이션 뒤에 물음표를 추가하여 표현할 수도 있습니다. Swift는 코드가 단축된것을 선호하므로 타입 뒤에 물음표를 쓰는것을 선호합니다.

let shortForm: Int? = Int("42")		// 위처럼 사용하는것이 선호됨
let longForm: Optional<Int> = Int("42")

 

위에서 Optional은 none과 some 케이스를 갖는 enum이라고 했습니다. Optional.none는 nil값과 동일합니다. Optional.some(Wrapped)은 래핑된 값을 저장합니다.

let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none
print(noNumber == nil)
print(number)

위 코드를 실행했을 때 number의 출력값이 Optional(42)인 것을 확인할 수 있습니다. 이 값은 Int값이 아닌 Optional값 42를 말합니다.

 

우리는 Optional값을 원래 데이터 값으로 변환하는 4가지 과정이 있습니다.

 

  1. Unconditional Unwrapping(무조건 언래핑)
  2. Using the Nil-Coalescing Operator(Nil-Coalescing 연산자 사용)
  3. Optional Binding(옵셔널 바인딩)
  4. Optional Chaining(옵셔널 체이닝)

 

Unconditional Unwrapping(무조건 언래핑)

Optional의 인스턴스에 값이 포함되어 있다고 확신하는 경우 강제 해제 연산자로 접미사에 느낌표(!)를 사용하여 Optional을 무조건 해제할 수 있습니다. 

 

예시를 통해 확인해보겠습니다.

아래 코드를 실행하면 number 옵셔널 변수에 5를 대입하고 값을 출력하면 Optional(5)라는 것을 확인할 수 있습니다.

옵셔널값에 값이 있다는 것을 알 수 있기 때문에 무조건 언래핑을 이용하여 number를 언래핑한 값을 출력하면 5가 출력되었음을 확인할 수 있습니다.

var number: Int? = 5
print(number)		// Optional(5)
print(number!)		// 5

하지만 무조건 언래핑은 옵셔널 값이 none이 아니라는 가정을 하고 무조건 언래핑을 하게되면 컴파일 에러는 일어나지 않지만, 런타임 에러가 발생할 수 있습니다.

 

예시를 통해 확인해보겠습니다.

아래 코드와 같이  nilNumber를 Int 옵셔널 타입으로 정의하고 값을 할당하는것을 까먹은채 출력을 해보는 코드를 수행해보겠습니다.

nilNumber를 출력했을 때 nil이 출력되면서 변수에 값이 없는 상태입니다. 이때, 무조건 언래핑을 통해 변수값을 출력하면 에러가 발생하는것을 확인할 수 있습니다. 따라서 값이 없을 때는 다른 옵셔널 언래핑 방법을 사용해야 합니다.

var nilNumber: Int?
print(nilNumber)		// nil
print(nilNumber!)		// error!

Using the Nil-Coalescing Operator(Nil-Coalescing 연산자 사용)

Optional 인스턴스가 nil인 경우 nil-coalescing 연산자(??)를 사용하여 기본값을 제공합니다. 

 

예시를 통해 확인해보겠습니다.

위에서 아래 코드를 실행했을 때 오류가 발생하는 것을 확인할 수 있었습니다. 이것을 nil-coalescing 연산자를 사용하여 nil인 경우에 기본값을 제공해 보겠습니다.

var nilNumber: Int?
print(nilNumber)		// nil
print(nilNumber!)		// error!

아래 코드에서 nilNumber가 nil값이라는 것을 알고, 변수의 값이 nil일 때 default value 값을 출력시킵니다.

오류가 발생하지 않고 둘다 nil이 출력된 것을 확인할 수 있습니다.

var nilNumber: Int?
print(nilNumber)		// nil
print(nilNumber ?? "nil")	// nil

Optional Binding(옵셔널 바인딩)

Optional 인스턴스의 래핑된 값을 새 변수에 조건부로 바인딩하려면 if let, guard let 및 switch를 비롯한 선택적 바인딩 제어 구조 중 하나를 사용합니다.

 

아래 코드는 if let을 이용한 옵셔널 바인딩을 수행한 것입니다. 옵셔널 언래핑을 통해 42가 출력된 것을 확인할 수 있습니다.

let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none

if let checkNumber = number {
    print("number is \(checkNumber)")
} else {
    print("no number")
}

아래 코드는 guard let 을 이용하여 Optional을 언래핑한 것입니다.

func check(number: Int?) {
    guard let checkNumber = number else {
        print("no number")
        return
    }
    print("number is \(checkNumber)")
}

let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none

check(number: number)
check(number: noNumber)

 

if let 과 guard var의 차이점

if let의 경우 Optional 값이 있거나 없는경우 두가지 모두 구현할 수 있고 다음 코드까지 실행 가능하다는 장점이 있습니다.

 

하지만 if let의 변수는 중괄호 안에서만 사용 가능하다는 단점이 있습니다.

아래 코드를 실행하면 checkNumber를 찾을 수 없다는 오류를 발생시킵니다.

let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none


if let checkNumber = number {
    print("number is \(checkNumber)")
}
else {
    print("no number")
}

print(checkNumber)

guard var의 경우 만약 언래핑한 값이 none 즉 nil 값이라면 else문이 수행되고 값이 있다면 else 다음 줄부터 실행이 됩니다. 

옵셔널 바인딩을 guard var로 처리하는 경우 가독성이 향상된다는 장점이 있습니다.

하지만 else문에서는 반드시 return 또는 throw가 필요합니다. 

 

아래는 guard let을 통하여 구현한 코드입니다. 

실행했을 때 guard let의 body는 통과하면 안되고, return과 throw가 필요하다고 알려주고 있습니다.

let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none

guard let checkNumber = number else {
    print("no number")
}

오류를 해결하기 위해 함수에 guard let을 선언하겠습니다.

오류가 사라졌음을 확인할 수 있고 if let과는 다르게 guard let 중괄호 이후에서도 checkNumber를 사용할 수 있는것을 확인할 수 있습니다.

func check(number: Int?) {
    guard let checkNumber = number else {
        print("no number")
        return
    }
    print("number is \(checkNumber)")
}

let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none

check(number: number)
check(number: noNumber)

따라서 if let은 변수가 nil인 경우, nil이 아닌 경우 둘다 다뤄주고, guard let은 변수가 nil인 경우는 더 이상 코드를 실행하지 않고 return 또는 throw를 수행하고 nil이 아닌 경우 코드를 이어서 수행하게 됩니다.

 

Optional Chining(옵셔널 체이닝)

Optional로 래핑된 인스턴스의 속성과 메서드에 안전하게 액세스하려면 후위 옵셔널 체이닝 연산자(?)를 추가하여 액세스 할 수 있습니다.

즉, 옵셔널로 래핑된 인스턴스의 속성과 메서드에 액세스할 때 뒤에 물음표를 붙여서 만약 그 값이 nil값이라면 nil을 리턴하고 값이 존재한다면 값을 리턴하게 됩니다.

 

예제를 통해 확인해보겠습니다.

Student 클래스를 선언하고 student 변수에 Student 인스턴스를 옵셔널 변수에 할당하고 studentID를 출력합니다.

student는 옵셔널 타입이기 때문에 아까 배웠던 옵셔널 바인딩으로 해결할 수 있습니다.

class Student {
    var studentID: String?
    var name: String?

    init(studentID: String, name: String) {
        self.studentID = studentID
        self.name = name
    }
}

var student: Student? = Student(studentID: "010101010", name: "홍길동")
print(student.studentID)	// 출력

하지만 옵셔널 바인딩으로 해결하려면 아래처럼 코드가 길어진다는 단점이 있습니다.

if let student = student {
    if let studentId = student.studentID {
        print(studentId)	// 010101010
    }
}

그래서 아래처럼 student 인스턴스 뒤에 후위 옵셔널 체이닝 연산자(?)를 추가하여 간단하게 해결할 수 있습니다.

studentID가 올바르게 출력되었음을 확인할 수 있습니다.

if let studentId = student?.studentID {
    print(studentId)	// 010101010
}

 

Comments