[Swift] 스위프트 기초 - 옵셔널

koi·2022년 10월 3일
0
post-thumbnail

옵셔널

  • 옵셔널은 스위프트의 특징 중 하나인 안전성 을 문법으로 담보하는 기능
  • 단어 뜻 그대로 '선택적인', 즉 값이 '있을 수도, 없을 수도 있음'을 나타내는 표현
  • 변수나 상수 등에 꼭 값이 있다는 것을 보장할 수 없음.
    즉, 변수 또는 상수의 값이 nil일 수도 있다는 것을 의미
  • 옵셔널과 옵셔널이 아닌 값은 다른 철저히 다른 타입으로 인식
    → 컴파일할 때 바로 오류를 걸러낼 수 있음

옵셔널 사용

변수, 상수에 정말 값이 없을 때만 nil로 표현합니다.
(0이나 "" 도 하나의 값으로, 값이 없는 것은 아님)

함수형 프로그래밍 패러다임에서 자주 등장하는 모나드 개념과 일맥상통합니다.

값이 없는 옵셔널 변수, 상수에 강제로 접근하려면 런타임 오류가 발생합니다.

nil은 옵셔널로 선언된 곳에서만 사용할 수 있습니다
옵셔널은 데이터 타입 뒤에 물음표 ?를 붙여 표현해줍니다.

어떤 상황에 옵셔널을 사용해야할까?

  • 간단히 nil을 반환해서 오류가 있음을 알릴 수 있다.
  • 매개변수를 굳이 넘기지 않아도 된다는 뜻으로 매개변수의 타입을 옵셔널로 정의할 수 있다.

enum school: String {
	case primary = "유치원"
    case elementary = "초등학교"
    case middle = "중학교"
    case high = "고등학교"
    case college = "대학"
    case university = "대학교"
    case grauate = "대학원"
}

let primary = School(rawValue: "유치원") // primary
let graduate = School(rawValue: "석박사") // nil

데이터 타입을 명시하지 않고 타입 추론 기능을 사용한 예제입니다.
컴파일러는 primary, graduate의 데이터 타입을 School? 이라고 추론했을 것입니다.
이 때 원시 값이 열거형의 case에 해당하지 않으면 열거형 인스턴스 생성에 실패하여 nil을 반환하는 경우가 생깁니다.

옵셔널 열거형의 정의

옵셔널은 열거형으로 정의되어있습니다.

public enum Optional<Wrapped> : ExpressibleByNilLiteral {
	case none
    case some(Wrapped)
    public init(_ some: Wrapped)
    /// 중략 ...
}

옵셔널은 값을 갖는 케이스그렇지 못한 케이스 두가지로 정의되어 있습니다.

  • nil일 때는 none 케이스
  • 값이 있는 경우는 some 케이스, 연관값으로 Wrapped

따라서 옵셔널에 값이 있으면 some의 연관값인 Wrapped에 값이 할당됩니다.
즉 값이 옵셔널이라는 열거형의 방패막에 보호되어 래핑되어 있는 모습입니다.

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

switch를 통한 옵셔널 값의 확인

func checkOptionalValue(value optionalValue: Any?) {
	switch optionalValue {
    	case .none:
        	print("This optional variable is nil")
    	case .some(let value):
        	print("Value is \(value)")
    }
}

var myName: String? = "toma"
checkOptionalValue(value: myName) // Value is toma

myName = nil
checkOptionalValue(value: myName) // This optional variable is nil

여러 케이스의 조건을 통해 검사하고자 한다면 where 절과 병합해서 쓰면 좋습니다.


let numbers: [Int?] = [2, nil, -4, nil, 100]

for number in numbers {
	switch number {
    case .some(let value) where value < 0:
    	print("Negative value!! \(value)")
    case .some(let value) where value > 10:
    	print("Large value!! \(value)")
    
    case .some(let value):
    	print("Value \(value)")
    }
    
    case .none:
    	print("nil")
}

// Value 2
// nil
// Negative value!! -4
// Large value!! 100

그러나 단 하나의 옵셔널을 switch 구문을 통해 값이 있는지 확인하는 것을 불편하기에, 값을 좀 더 안전하고 편리하게 추출하는 방법에 대해 알아보겠습니다.


옵셔널 추출

열거형의 some 케이스로 꼭꼭 숨어있는 옵셔널의 값을 옵셔널이 아닌 값으로 추출하는 여러가지 옵셔널 추출 방법들이 있습니다.

강제 추출

옵셔널 강제 추출 방식은 가장 간단하지만 가장 위험합 방법입니다.
런타임 오류가 일어날 가능성이 가장 높기 때문입니다.

옵셔널의 값을 강제 추출하려면 옵셔널 값의 뒤에 느낌표 ! 를 붙여주면 됩니다.
만약 강제 추출 시 옵셔널에 값이 없다면, 런타임 오류가 발생합니다.

옵셔널이 아닌 변수애는 옵셔널 값이 들어갈 수 없어서 추출해서 할당해주어야 합니다.

nil을 강제 추출했기 때문에 런타임 오류가 발생합니다.

if 구문 등 조건문을 이용해서 조금 더 안전하게 강제 추출하는 방법입니다.


옵셔널 바인딩

if 구문을 통해 값이 nil인지 먼저 확이나혹 옵셔널 값을 강제 추출하는 방법은 옵셔널 사용 이유가 무의미합니다.

스위프트는 조금 더 안전하고 세련된 방법으로 옵셔널 바인딩 을 제공합니다.

만약 옵셔널에 값이 있다면 옵셔널에서 추출한 값을 일정 블록 안에서 사용할 수 있는 상수나 변수로 할당해서 옵셔널이 아닌 형태로 사용할 수 있도록 해줍니다.

  • if 또는 while 구문과 결합하여 사용할 수 있습니다.
  • if 구문을 실행하는 블록 안쪽에서만 name이라는 임시 상수를 사용할 수 있습니다.
  • 상수를 사용하지 않고 if var를 통해 임시 변수로 할당할 수도 있습니다.
  • 쉼표 , 를 통해한번에 여러 옵셔널의 값을 추출할 수 있습니다.
    단, 바인딩하려는 옵셔널 중 하나라도 값이 없다면 해당 블록 내부의 명령문은 실행되지 않습니다.
var myName: String? = "toma"

if let name = myName {
    print("My name is \(name)")
} else {
    print("myName == nil")
}

var yourName: String? = nil

// 옵셔널 바인딩을 사용해 여러 개의 옵셔널 값 추출
if let name = myName, let friend = yourName {
    print("We are friend! \(name) & \(friend)")
}

// My name is toma

옵셔널 바인딩과 함께 사용되는 옵셔널 체이닝에 대한 내용은 다른 14장 옵셔널 체이닝과 빠른 종료에서 다룸


암시적 추출 옵셔널

nil을 할당하고 싶지만, 옵셔널 바인딩으로 매번 값을 추출하기 귀찮거나 로직상 nil 때문에 런타임 오류가 발생하지 않는다는 확신이 들 때

nil을 할당해줄 수 있는 옵셔널이 아닌 변수, 상수에 사용하는 것이 암시적 추출 옵셔널 입니다.

  • 암시적 추출 옵셔널을 사용하려면 타입 뒤에 느낌표 ! 를 사용하면 됩니다.
  • 일반 값처럼 사용할 수 있으나, 여전히 옵셔널이기 때문에 nil도 할당해줄 수 있습니다.
  • nil일 때 접근하면 런타임 오류가 발생합니다.
var myName: String! = "toma"
print(myName) // Optional(toma)
myName = nil

// 암시적 추출 옵셔널도 옵셔널이므로 바인딩을 사용할 수 있습니다.
if let name = myName { // myName == nil
    print("My name is \(name)")
} else {
    print("myName == nil")
}

myName.isEmpty // 오류

이렇게 그냥 옵셔널 값이든, 암시적 추출 옵셔널 값이든 옵셔널인건 똑같지만,
암시적 추출 옵셔널 값은 추출이 필요없다는 사실을 알 수 있습니다!

profile
Don't think, just do 🎸

0개의 댓글