Chapter 6: Optionals
Version
Swift 5.5, iOS 15, Xcode 13
지금까지 다뤘던(dealt with) 모든 변수(variables)와 상수(constants)는 구체적인 값(concrete values)을 가지고 있었다. var name
과 같은 문자열(string) 변수(variable)가 있다면 "Matt Galloway"
와 같은 문자열(string) 값(value)이 연결(associated)된다. ""
와 같은 빈 문자열(empty string)도, 참조(refer)할 수 있는 값(value)이 있었다.
이것이 Swift에 내장된(built-in) 안전 기능(safety features) 중 하나이다. 유형(type)이 Int
또는 String
이라면, 실제(actual) 정수(integer) 또는 문자열(string)이 보장(guaranteed)된다.
이 장(chapter)에서는 값(value)과 해당 값(value)의 부재(absence)를 나타낼(represent) 수 있는 특별한 Swift 유형(type)인 optional 개념(concept)을 소개(introduce)한다. 이 장(chapter)을 마치면, 옵셔널(optionals)이 필요한 이유와 이를 안전하게(safely) 사용하는 방법을 알게 될 것이다.
Introducing nil
때로는(sometimes), 값(value)의 부재(absence)를 나타내는(represent) 것이 유용(useful)하다. 사람의 신원 정보(identifying information)를 참조(refer to)해야 하는 상황(scenario)이 있다면, 그 사람의 이름(name), 나이(age), 직업(occupation)을 저장(store)해야 한다. 이름과 나이는 모든 사람이 가지고 있으므로 반드시 값(value)이 있어야 한다. 하지만 모든 사람이 취업(employed)한 것은 아니므로, 직업(occupation)에 대한 값(value)이 없음(absence)을 처리(handle)해야 한다.
옵셔널(optionals)을 사용하지 않고, 다음과 같이 사람의 이름(name), 나이(age), 직업(occupation)을 나타낼 수 있다:
var name = "Matt Galloway"
var age = 30
var occupation = "Software Developer & Author"
하지만 실업자(unemployed)가 될수도, 복권(lottery)에 당첨되어 아예 일을 그만두고(give up) 싶을 수도 있다. 이 경우가 값(value)의 부재(absence)를 언급할(refer to) 유용한 시점이다.
빈 문자열(empty string)을 사용할 수도 있지만, 옵셔널(optionals)이 훨씬 더 나은 해결 방안(solution)이다. 그 이유를 계속 해서 읽으면서 확인해 본다.
Sentinel values
값(value)의 부재(absence)와 같은 특수 조건(special condition)을 나타내는(represents) 값(value)을 센티넬 값(sentinel value) 또는 간단히(simply) 특수 값(special value)이라고 한다. 이전 예제(example)에서의 빈 문자열(empty string)이 그럴 것이다.
또 다른 예(example)를 살펴본다. 코드가 서버(server)에서 무언가를 요청(requests)하고, 변수(variable)를 사용하여 반환된 오류 코드(returned error code)를 저장(store)한다고 가정해 본다:
var errorCode = 0
성공(success)하는 경우에는 오류 없음(lack of an error)을 0(zero)로 나타낸다. 즉, 0
은 센티넬 값(sentinel value)이다. 위의 예에서 직업(occupation)을 빈 문자열(empty string)로 표현할 수 있지만, 제멋대로(arbitrarily) 값(value)을 사용한 것이기 때문에 프로그래머에게 잠재적으로(potentially) 혼동(confusing)을 줄 수 있다. 0
은 유효한 오류 코드(error code)이거나, 또는 미래(future)의 서버 응답(responded) 방식이 변경될 수도 있다. 어느 쪽이든(either way), 특수 값(special values)에 대한 문서(documentation)를 참조(consulting)하지 않고 서버가 오류(error)를 반환(return)하지 않았다고 완전히(completely) 확신(confident)할 수 없다.
이 두 가지 예(examples)에서, 값의 부재(absence of a value)를 나타낼(represent) 수 있는 특수 유형(special type)이 있다면 훨씬 더 나을 것이다. 값이 있다면(exists) 명시적(explicit)이고, 없다면 컴파일러(compiler)가 이를 확인할 수 있을 것이다.
Nil은 값이 없을 때(absence of a value) 사용하는(given) 이름이며, Swift가 어떻게 이 개념(concept)을 우아한(elegant) 방식으로 직접(directly) 통합(incorporates)했는지 확인하게 될 것이다.
일부 다른 프로그래밍 언어(programming languages)는 단순히 센티넬 값(sentinel values)을 수용(embrace)한다. Objective-C의 경우, nil
의 개념(concept)이 있지만 이는 0(zero)의 동의어(synonym)이자 또 다른 센티넬 값(sentinel value)일 뿐이다.
Swift는 값(value)이 nil
일 수 있는 가능성(possibility)을 처리(handles)하는 완전히 새로운 유형(whole new type)인 Optional을 도입(introduces)했다. 옵셔널이 아닌 유형(non-optional type)을 처리(handling)하는 경우, 값(value)이 보장(guaranteed)되므로 특별한 의미의 센티넬 값(sentinel value)에 대해 걱정할 필요가 없다. 옵셔널 유형(optional type)을 사용하는 경우에는 nil
케이스(case)를 처리(handle)해야 한다. 센티넬 값(sentinel values)을 사용하여 도입된(introduced) 모호성(ambiguity)을 제거(removes)한다.
Introducing optionals
옵셔널(optionals)은 값(value)과 값의 부재(absence of a value)를 모두 나타내는(representing) 문제(problem)에 대한 Swift의 해결책(solution)이다. 옵셔널(optional)은 값(value)이나 nil
을 가질 수 있다.
옵셔널(optional)을 정확히(exactly) 하나의 값(value)이 있거나((contains)) 비어(empty) 있는 상자(box)로 생각할 수 있다. 값(value)을 포함(contain)하지 않으면 nil
이다. 상자 자체는 항상(always) 존재(exists)한다. 항상(always) 열어서 내부(inside)를 볼 수 있다.
그러나(on the other hand), 문자열(string)이나 정수(integer)는 이런 상자(box)가 없다. 대신 "hello"
또는 42와 같은 값(value)이 항상 존재한다. 옵셔널이 아닌 유형(non-optional types)은 실제 값(actual value)이 보장(guaranteed)된다는 점을 기억해야 한다.
Note: 물리학(physics)을 공부했다면 지금 슈뢰딩거의 고양이(Schroedinger’s cat)를 떠올렸을 수도 있다. 옵셔널(optionals)이 생사(life and death)가 달린 문제가 아니라는 점만 뺀다면 비슷하다.
다음 구문을 사용하여 옵셔널(optional) 유형(type)의 변수(variable)를 선언(declare)한다:
var errorCode: Int?
일반(standard) 선언(declaration)과의 유일한 차이점(only difference)은 유형(type)의 끝에 있는 물음표(question mark)이다. 이 경우(in this case), errorCode
는 "optional Int
"이다. 이것은 변수(variable)가 Int
또는 nil
을 포함(containing)하고 있는 상자(box)와 같다는 의미이다.
Note: 모든 유형(type) 뒤에 물음표(question mark)를 추가하여 옵셔널 유형(optional type)을 생성할 수 있다. 이 옵셔널 유형(optional type)은 옵셔널이 아닌 일반 유형(regular non-optional type)을 래핑한다고 말한다. 예를 들어(for example), 옵셔널 유형(optional type)인String?
은String
을 래핑(wraps)한다. 즉(in other words),String?
유형(type)의 옵셔널(optional) 상자(box)에는String
또는nil
가 존재한다(holds).
또한,:Int?
와 같은 유형(type) 주석(annotation)을 사용(note)하여 옵셔널 유형(optional type)을 명시적(explicit)으로 만들 수 있다. 옵셔널 유형(optional types)의 이러한 값(values)은 일반(regular), 옵셔널이 아닌 유형(non-optional type) 또는nil
이기 때문에 초기화(initialization) 값(values)에서 추론(inferred)할 수 없다.
값(value)을 설정하는 것은 간단하다. 다음과 같이 Int
로 설정할 수 있다:
errorCode = 100
또는 다음과 같이 nil
로 설정할 수 있다:
errorCode = nil
이 다이어그램(diagram)은 무슨 일이 일어나고 있는지 시각화(visualize)하는 데 도움이 될 수 있을 것이다:
옵셔널(optional) 상자(box)는 항상 존재(exists)한다. 변수(variable)에 100
을 할당(assign)하면, 상자(box)에 값(value)이 채워(filling)지고, nil
을 할당(assign)하면 상자(box)가 비게(emptying) 된다.
잠시 시간을 내어(take a few minutes) 이 개념(concept)에 대해 생각해 본다. 상자(box) 비유(analogy)는 이 장(chapter)의 나머지 부분을 살펴보고(go through), 옵셔널(optionals)을 사용할 때 큰 도움이 될 것이다.
Mini-exercises
1. myFavoriteSong
이라는 옵셔널 String
을 생성한다. 좋아하는(favorite) 노래(song)가 있으면 해당 노래(song)를 문자열(string)로 설정한다. 좋아하는(favorite) 노래가 두 개 이상이거나 없다면 옵셔널(optional)을 nil
로 설정한다.
2. parsedInt
라는 상수(constant)를 생성(create)하고 Int("10")
으로 설정하면, 문자열(string) 10
을 파싱(parse)하여 Int
로 변환(convert)하려고 한다. Option-Click을 사용하여 parsedInt
의 유형(type)을 확인해본다. 왜 옵셔널(optional)로 표시되는지 생각해 본다.
3. 위 예제(exercise)에서 파싱(parsed)되는 문자열(string)을 정수가 아닌(non-integer) 것으로 변경한다(예: dog
). 이제 parsedInt
는 어떤 유형인지 확인해 본다.
Unwrapping optionals
옵셔널(optionals)로 존재(exist)하는 것은 좋은 일이지만, 상자(box) 내부(inside)를 보고 포함(contains)된 값을 조작(manipulate)하는 방법이 궁금할 수 있다.
옵셔널(optional)의 값(value)을 출력(print out )할 때 어떤 일이 일어나는지 살펴본다:
var result: Int? = 30
print(result)
다음과 같이 출력된다(prints):
Optional(30)
Note: 이 행(line)에는 "표현식이 암시적으로 'Int?'에서 Any로 강제 변환됨(Expression implicitly coerced from ‘Int?’ to Any)"이라는 경고(warning)도 표시된다. Swift가Any
유형(type) 대신 옵셔널(optional)을 사용하고 있다고 경고하기 때문인데, 이는 일반적으로 무언가 잘못(wrong)됐다는 의미이다. 코드를print(result as Any)
로 변경하여 해당 경고(warning)를 해결(silence)할 수 있다.
말이 되긴 하지만(it makes sense), 원했던 결과는 아니다. 상자(box)를 출력(printed)하면 "result
는 값(value) 30
을 포함(contains)하는 옵셔널(optional)이다" 라고 표시된다.
옵셔널(optional) 유형(type)이 옵셔널이 아닌 유형(non-optional type)과 어떻게 다른지, 해당 result
를 일반(normal) 정수(integer)처럼 사용하려고 하면 어떻게 되는지 확인해본다:
print(result + 1)
이 코드(code)는 오류(error)를 발생(triggers)시킨다:
Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
상자(box)에 정수(integer)를 추가(add)하려 하기 때문에 동작하지 않는다. 이는 상자(box) 안(inside)의 값(value)이 아니라 상자 자체(box itself)에 정수(integer)를 추가하려 하므로 의미가 없다(doesn’t make sense).
Force unwrapping
오류 메시지(error message)는 옵셔널(optional)을 풀어야(unwrapped) 한다(must be unwrapped
)는 해결 방법(solution)을 알려준다(indicates). 상자(box)에서 값(value)을 풀어야(unwrap) 한다. 마치 크리스마스 같다! 어떻게 작동(works)하는지 확인해 본다.
다음 선언(declarations)을 고려해 본다:
var authorName: String? = "Matt Galloway"
var authorAge: Int? = 30
이러한 옵셔널(optionals)을 해제(unwrap)하는 데 사용할 수 있는 두 가지 방법(methods)이 있다. 첫째는 강제 해제(force unwrapping)라고 하며, 다음과 같이 수행(perform)한다:
var unwrappedAuthorName = authorName!
print("Author is \(unwrappedAuthorName)")
위 코드는 다음과 같이 출력(prints)된다:
Author is Matt Galloway
기대한대로(expect) 출력이 된다.
변수(variable)명 뒤의 느낌표(exclamation mark)는 컴파일러(compiler)에게 상자(box) 안(inside)을 살펴보고 값(value)을 꺼낼(take out) 것을 알려준다. 이 결과(result)는 래핑(wrapped)된 유형(type)의 값(value)이다. 이는 unwrappedAuthorName
이 String?
이 아니라 String
유형(type)임을 의미한다.
"강제(force)" 라는 단어와 느낌표(exclamation mark) !
를 사용하는 것은 아마(probably) 위험(danger)한 감각을 전달(conveys)할 것이고 그래야 한다.
강제 해제(force unwrapping)는 되도록 적게(sparingly) 사용해야 한다. 옵셔널(optional)에 값(value)을 포함(contain)하지 않을 때 무슨 일이 일어나는지 고려(consider)해 본다:
authorName = nil
print("Author is \(authorName!)")
이 코드(code)는 콘솔(console)에 다음과 표시되는 오류(error)를 생성(produces)한다:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
변수(variable)를 풀려고(unwrap) 할 때, 값(value)이 없기 때문에 오류가 발생(occurs)한다. 더 나쁜 것(worse)은 컴파일 타임(compile-time)이 아닌 런타임(runtime)에 이 오류(error)가 발생한다는 것이다. 이는 잘못된(invalid) 입력(input)으로 이 코드를 실행(execute)하는 경우에만 오류(error)를 알아차릴 수 있다는 의미(means)이다.
더 나쁜 것은(worse yet), 이 코드(code)가 앱(app) 내부(inside)에 있으면 런타임(runtime) 오류(error)로 인해 앱(app)이 충돌(crash)하게 된다.
안전한 방법이 필요하다.
여기서 런타임(runtime) 오류(error)를 막으려면(stop), 코드를 다음과 같이 옵셔널(optional)을 해제(unwraps)하는 검사(check)로 감싼다(wrap):
if authorName != nil {
print("Author is \(authorName!)")
} else {
print("No author.")
}
if
문(statement)은 옵셔널(optional)에 nil
이 포함(contains)되어 있는지 확인(checks)한다. 그렇지 않은 경우, 해제(unwrap)할 수 있는 값(value)이 포함(contains)되어 있다.
이제 코드(code)는 안전(safe)하지만, 여전히(still) 완벽(perfect)하지는 않다. 이 기법(technique)을 사용(rely on)하면, 옵셔널(optional)을 풀고(unwrap) 싶을 때마다 nil
을 확인(check for)해야 한다. 이것은 지겨운(tedious) 일이고, 언젠가는 이를 잊어버리고(forget) 결국 다시 런타임(runtime) 오류(error)의 가능성이 남게 될 것이다.
처음부터 다시 시작해 본다(back to the drawing board).
Optional binding
Swift에는 옵셔널 바인딩(optional binding)이라는 기능(feature)이 포함되어 있어, 옵셔널(optional) 내부(inside)의 값(value)에 안전하게(safely) 접근(access)할 수 있다. 다음과 같이 사용한다:
if let unwrappedAuthorName = authorName {
print("Author is \(unwrappedAuthorName)")
} else {
print("No author.")
}
여기에는 느낌표(exclamation marks)가 없다는 것을 즉시(immediately) 알 수(notice) 있다. 이 옵셔널 바인딩(optional binding)은 옵셔널(optional) 유형(type)을 제거(gets rid of)한다. 옵셔널(optional)에 값(value)이 포함(contains)된 경우, 이 값(value)은 해제되어(unwrapped) 상수(constant) unwrappedAuthorName
에 저장(stored in)되거나 바인딩(bound to)된다. 그런 다음 if
문(statement)은 코드의 첫 번째 블록(block)을 실행(executes)한다. 이 블록 내에서 unwrappedAuthorName
은 일반(regular non-optional) String
이므로 안전하게(safely) 사용할 수 있다.
옵셔널(optional)에 값(value)이 없다면(contain), if
문(statement)은 else
블록(block)을 실행(executes)한다. 이 경우(in that case)에는, unwrappedAuthorName
변수(variable)가 존재(exist)하지 않는다.
옵셔널 바인딩(optional binding)이 강제 해제(force unwrapping)보다 훨씬 안전하다는 것을 알 수 있으며, 옵셔널(optional)이 nil
일 가능성이 있을 때마다(whenever) 이를 사용한는 것이 좋다. 강제 해제(force unwrapping)는 옵셔널(optional)이 값(value)을 가진 것(contain)이 보장(guaranteed)된 경우에만 사용한다.
이름을 붙이는 것이 너무 어렵기 때문에, 해제되지 않은(unwrapped) 상수(constant)에 옵셔널(optional)과 동일한 이름을 지정(give)하는 것이 일반적(common)이다(thereby shadowing that optional):
if let authorName = authorName {
print("Author is \(authorName)")
} else {
print("No author.")
}
다음과 같이 여러(multiple) 값(values)을 동시에 해제(unwrap)할 수도 있다:
if let authorName = authorName,
let authorAge = authorAge {
print("The author is \(authorName) who is \(authorAge) years old.")
} else {
print("No author or no age.")
}
이 코드는 두 값(values)을 해제(unwraps)한다. 두 옵셔널(optionals)에 모두 값(value)이 포함(contain)된 경우에만 if
문(statement)의 해당 부분을 실행한다.
여러(multiple) 옵셔널(optionals)의 해제(unwrapping)와 추가적인(additional) 부울(Boolean) 검사(checks)를 함께 결합(combine)할 수 있다. 예를 들면 다음과 같다(for example):
if let authorName = authorName,
let authorAge = authorAge,
authorAge >= 40 {
print("The author is \(authorName) who is \(authorAge) years old.")
} else {
print("No author or no age or age less than 40.")
}
여기서(here), name
과 age
를 해제(unwrap)하고, age
가 40 이상인지(greater than or equal to) 확인(check)한다. if
문(statement)의 표현식(expression)은 name
과 age
가 nil
이 아니고(non-nil), age
가 40 이상(greater than or equal to)인 경우에만 true
가 된다.
이제 옵셔널(optional)의 내부(inside)를 안전하게(safely) 살펴보고, 해당 값(value)이 있는(exists) 경우 추출(extract)하는 방법을 알게 되었다.
Mini-exercises
1. 이전(earlier)의 myFavoriteSong
변수(variable)에 옵셔널 바인딩(optional binding)을 사용하여 값(value)이 포함(contains)되어 있는지 확인(check)한다. 만약 그렇다면 값(value)을 출력(print out)하고, 그렇지 않다면 "I don’t have a favorite song."
라고 출력(print)한다.
2. myFavoriteSong
을 현재와 반대로(opposite of) 변경(change)한다. nil
이면 문자열(string)로 설정하고, 문자열(string)이면 nil
로 설정한다. 출력된(printed) 결과(result)가 어떻게 변하는지(changes) 확인(observe)한다.
Introducing guard
옵셔널(optionals)을 사용할 때처럼 조건(condition)을 확인하고, 참(true)인 경우에만 함수(function)를 계속 실행(executing)하고 싶을 때가 있다. 네트워크(network)에서 일부 데이터를 가져오는 함수(function)를 상상해 보면(imagine), 네트워크(network)가 다운(down)되었을 때 해당 가져오기(fetch)가 실패(fail)할 수 있다. 이 동작을 캡슐화(encapsulate)하는 일반적인 방법은 가져오기(fetch)가 성공(succeeds)하면 값이 있고, 그렇지 않으면(otherwise) nil
인 옵셔널(optional)을 사용하는 것이다.
Swift에는 이와 같은 상황(situations)에서 도움이 되는 유용(useful)하고 강력(powerful)한 기능(feature)인 guard
문(statement)이 있다. 다음 예제와 함께 살펴본다:
func guardMyCastle(name: String?) {
guard let castleName = name else {
print("No castle!")
return
}
// At this point, `castleName` is a non-optional String
print("Your castle called \(castleName) was guarded!")
}
guard
문(statement)은 guard와 부울(Boolean) 표현식(expressions)과 옵셔널 바인딩(optional bindings)을 모두 포함(include)할 수 있는 조건(condition), else
, 코드 블록(block of code)으로 구성(comprises)된다. 조건(condition)이 false인 경우 else
가 적용(covered)되는 코드 블록(block of code)이 실행(execute)된다. 조건이 false인 경우, 실행(executes)되는 코드 블록(block of code)은 반드시 반환(return)되어야 한다. 실수로(accidentally) 이를 잊어버리면(forget), 컴파일러(compiler)가 알려준다(stop). 이것이 가드(guard) 문(statement)의 진정한 아름다움(beauty)이다.
프로그래머(programmers)가 함수(function)를 사용한(through) "행복한 경로(happt path, 정상적인 사전 조건과 테스트 스텝 및 기대 결과)"에 대해 이야기하는 것을 들어 봤을 것이다. 이는 대부분의 경우(most of the time) 발생할 것으로 예상되는(expect to) 경로(path)이다. 다른 경로(path)는 오류(error) 또는 함수(function)가 예상(expected)보다 빨리 반환(return)되는(due to) 다른 이유(reason)로 인해 발생한다.
가드(guard) 문(statements)은 행복한 경로(happy path)가 코드의 왼쪽(left-hand side)에 남도록(remains) 보장(ensure)한다. 이는 코드(code)를 더 읽기 쉽고(readable) 이해하기(understandable) 쉽게 만들기 때문에 일반적으로(usually) 좋은 방법이다. 또한(also), 가드(guard) 문(statement)은 거짓(false)의 경우(case)를 반환(return)해야 하기 때문에 Swift 컴파일러(compiler)는 조건(condition)이 참(true)이면 가드(guard) 문(statement)의 조건(condition)에서 확인(checked)된 모든 것이 함수(function)의 나머지 부분(remainder)에서 참(true)이어야 한다는 것을 알고 있다.
이는 컴파일러(compiler)가 특정한(specific) 최적화(optimizations)를 수행할 수 있다는 것을 의미한다. Swift는 사용자 친화적(user-friendly)이고 빠르게(fast) 설계(designed to)되었기 때문에 이러한 최적화가 어떻게 작동(work)하는지 또는 무엇인지 이해(understand)할 필요가 없다.
단순히(simply) if-let
바인딩(binding)을 사용하고 nil
일 때 반환(return)할 수 있다. 그러나(however), guard
를 사용하면 guard
문(statement)이 거짓(false)일 때 이를 반환(return)해야 한다고 명시할 수 있다(explicitly saying). 따라서(thus) 컴파일러(compiler)는 반환(return)을 추가(added)했는지 확인(make sure)할 수 있다. 컴파일러(compiler)는 개발자에게 좋은 안전성(safety)을 제공(providing)한다.
좀 더 "실제적(real world)"인 예제(example)에서의 가드(guard)를 본다. 다음 함수(function)를 확인(consider)한다:
func calculateNumberOfSides(shape: String) -> Int? {
switch shape {
case "Triangle":
return 3
case "Square":
return 4
case "Rectangle":
return 4
case "Pentagon":
return 5
case "Hexagon":
return 6
default:
return nil
}
}
이 함수(function)는 도형(shape)의 이름(name)을 받아 도형(shape)의 면(sides) 수를 반환(returns)한다. 도형(shape)을 알 수 없거나(known) 도형(shape)이 아닌 것을 전달(pass)하면 nil
을 반환(returns)한다.
다음과 같이 이 함수(function)를 사용할 수 있다:
func maybePrintSides(shape: String) {
let sides = calculateNumberOfSides(shape: shape)
if let sides = sides {
print("A \(shape) has \(sides) sides.")
} else {
print("I don’t know the number of sides for \(shape).")
}
}
이는 아무런 문제가 없으며(nothing wrong), 잘 작동(work)한다.
그러나(however) 다음과 같은 가드(guard) 문(statement)으로 동일한 논리(logic)를 작성(written)할 수 있다:
func maybePrintSides(shape: String) {
guard let sides = calculateNumberOfSides(shape: shape) else {
print("I don’t know the number of sides for \(shape).")
return
}
print("A \(shape) has \(sides) sides.")
}
함수(functions)가 더 복잡(complex)해지면, guard
를 저절로 사용하게 될 것이다. 함수(function) 상단(top)에 초기(initial) 조건(conditions)을 올바르게(correctly) 설정(set up)하는 여러(multiple)개의 guard
가 있을 수 있다. 이는 Swift 코드(code)에서 광범위하게(extensively) 사용되는 것을 볼 수 있을 것이다.
Nil coalescing
옵셔널(optional)을 푸는(unwrap) 편리한(handy) 대안(alternative)이 있다. 무엇이든 옵셔널(optional)에서 값(value)을 얻고 싶을 때 사용한다. nil
인 경우(case), 기본값(default value)을 사용한다. 이를 nil 병합(coalescing)라고 한다. 작동 방식은 다음과 같다:
var optionalInt: Int? = 10
var mustHaveResult = optionalInt ?? 0
nil
병합(coalescing)은 nil 병합 연산자(coalescing operator)로 알려진 이중 물음표(??
, double question mark)를 사용한 두 번째 행(line)에서 발생한다(happens on). 이 행(line)은 mustHaveResult
가 optionalInt
내부(inside)의 값(value)과 동일해야함(equal)을 의미하며, optionalInt
에 nil
이 포함(contains)되어 있다면 0
이 된다. 이 예(example)에서 mustHaveResult
는 10
이라는 구체적인(concrete) Int
값(value)을 포함(contains)한다.
이 코드(previous code)는 다음과 같다(equivalent):
var optionalInt: Int? = 10
var mustHaveResult: Int
if let unwrapped = optionalInt {
mustHaveResult = unwrapped
} else {
mustHaveResult = 0
}
optionalInt
를 nil
로 설정(set)하면 다음과 같다:
optionalInt = nil
mustHaveResult = optionalInt ?? 0
이제 optionalInt
는 0
이 된다.
Challenges
다음 단계로 넘어가기 전에(before moving on), 옵셔널(optionals)에 대한 지식(knowledge)을 확인(test)하기 위한 몇 가지 챌린지(challenges)가 있다. 스스로 해결(solve)해 보려고 하는 것이 가장 좋지만, 막힌다면(get stuck) 다운로드(download)나 책의 소스 코드 링크(source code link)에서 해답(solutions)을 참고할 수 있다.
Challenge 1: You be the compiler
다음 중 올바른 구문(statements)이 무엇인지 생각해 본다.
var name: String? = "Ray"
var age: Int = nil
let distance: Float = 26.7
var middleName: String? = nil
Challenge 2: Divide and conquer
먼저(first), 정수(integer)를 나머지(remainder)가 없는 다른 정수(integer)로 나눌 수 있는 횟수를 반환(returns)하는 함수(function)를 생성한다. 나눗셈(division)이 정수(whole number)를 생성(produce)하지 않으면, 함수(function)는 nil
을 반환(return)해야 한다. 함수(function)은 이름을 DivideIfWhole
로 지정한다.
그런 다음(then), 함수(function)의 옵셔널(optional) 결과(result)를 해제(unwrap)하는 코드(code)를 작성(write)한다. 두 가지 경우(cases)가 있어야 한다. 성공하면(upon success) "Yep, it divides \(answer) times."
를 출력(print)하고, 실패하면(upon failure) "Not divisible :[."
을 출력(print)한다.
마지막으로(finally), 함수(function)을 테스트(test)한다:
1. 10을 2로 나누면(divide), "Yep, it divides 5 times."
가 출력(print) 되어야 한다.
2. 10을 3으로 나누면(divide), "Not divisible :[."
가 출력(print) 되어야 한다.
Hint 1: 함수(function) 시그니처(signature)로 다음을 사용한다:
func divideIfWhole(_ value: Int, by divisor: Int)
옵셔널(optional)이 될 반환(return) 유형(type)을 추가(add)해야 한다.
Hint 2: 나머지 연산자(modulo operator, %
)를 사용하여 해당 값(value)을 다른 값으로 나눌 수(divisible) 있는지 확인(determine)할 수 있다. 이 연산(operation)은 두 숫자를 나눈(division) 나머지(remainder)를 반환(returns)한다. 예를 들어(for example), 10 % 2 = 0
은 10을 나머지(remainder) 없이 2로 나눌 수 있음(divisible)을 의미하고, 10 % 3 = 1
은 10을 3으로 나누어(divisible) 나머지(remainder)가 1인 것을 의미한다.
Challenge 3: Refactor and reduce
마지막 챌린지(challenge)에서 작성한 코드(code)는 if
문(statements)을 사용했다. 이번 챌린지(challenge)에서는 대신 nil
병합(coalescing)을 사용하도록 코드(code)를 리팩터링(refactor)한다. 이번에는 모든 경우(cases)에 "It divides X times"
를 출력(print)하도록 하되, 나누기(division)의 결과가 정수(whole number)가 되지 않는다면 X
는 0
이어야 한다.
Challenge 4: Nested optionals
다음 중첩된(nested) 옵셔널(optional)을 확인해 본다. 이 값은 상자(box) 안(inside)의 상자(box) 안(inside)의 상자(box) 안(inside)의 숫자(number)에 해당(corresponds)한다.
let number: Int??? = 10
number
를 출력(print)하면 다음과 같다:
print(number)
// Optional(Optional(Optional(10)))
print(number!)
// Optional(Optional(10))
다음을 확인해 본다:
1. number
를 완전히 해제(unwrap)하고, 출력(print)한다.
2. if let
을 사용해 옵셔널 바인딩(optionally bind)하고, number
를 출력(print)한다.
3. 함수(function) printNumber(_ number: Int???)
를 작성한다. 해당 함수는 guard
를 사용하여 number
가 바인딩(bound)된 경우에만 이를 출력(print)한다.
Key points
nil
은 값(value)이 없음(absence)을 나타낸다(represents)- 옵셔널이 아닌(non-optional) 변수(variables)와 상수(constants)는 절대(never)
nil
이 아니다. - 옵셔널(optional) 변수(variables)와 상수(constants)는 값(value)을 포함(contain)하거나 비워(empty,
nil
)둘 수 있는 상자(boxes)와 같다. - 옵셔널(optional) 내부(inside)의 값(value)을 사용하려면, 먼저 옵셔널(optional)을 해제(unwrap)해야 한다.
- 옵셔널(optional) 값(value)을 해제(unwrap)하는 가장 안전한(safest) 방법은 옵셔널 바인딩(optional binding)이나 nil 병합(coalescing)을 사용하는 것이다. 런타임(runtime) 오류(error)가 발생(produce)할 수 있으므로, 강제 해제(forced unwrapping)는 적절한(appropriate) 경우에만 사용해야 한다.
guard let
을 사용하여 옵셔널(optional)을 바인딩(bind)할 수 있다. 바인딩(binding)이 실패(fails)하면, 컴파일러(compiler)는 강제로(forces) 현재(current) 함수(function)를 종료(exit)하거나 실행(execution)을 중지(halt)한다. 이렇게 하면 초기화되지 않은(uninitialized) 값(value)으로 프로그램(program)이 실행(executes)되지 않는다.