티스토리 뷰
이번 글에서는 Swift Optionals 에 대해 알아보겠습니다.
1. Optionals
Optionals 은 값을 가지지 않아도 되는 형식을 말합니다.
다음과 같은 특징을 가집니다.
1. Optional 에 저장되어 있는 값을 사용하려면 값을 Unwrapping 해야 한다.
2. nil 이 저장되어 있는 상태에서 강제 추출하면 에러가 발생한다.
3. Optional 표현식을 Unwrapping 하면 Non-Optional 타입으로 결과가 리턴된다.
지금까지 살펴본 코드에서 일반적으로 사용한 타입은 Non-Optional 타입으로 이 경우 변수와 상수는 반드시 초기화를 해서 사용해야 했습니다.
let num: Int
// print(num) - 에러
하지만 값을 미리 지정할 수 없는 경우도 존재합니다. (ex. 네트워크 데이터를 가져오는 경우 등)
이럴 때는 값을 가지지 않아도 되는 타입인 Optional 타입으로 변수와 상수를 선언하면 됩니다.
Optional 타입의 형식은 기존 자료형에 ? 를 붙인 형태입니다.
let optionalNum: Int? = nil
let optionalStr: String? = nil
// Non-Optional 타입은 항상 값을 가져야 함
let str: String = "Swift"
// let str: String = nil - 에러
위에서 Optional 타입의 초기값으로 사용된 nil 은 값이 없음을 의미합니다. Objective-C 의 nil 과는 다르게 포인터가 아니며 자체로는 추론할 형식이 없기 때문에 반드시 타입 annotation을 같이 사용하여야 합니다.
이런 Optional 타입을 그냥 사용할 수 없고 위에서 설명한 대로 값을 추출하여 (Unwrapping) 사용해야 합니다.
// Unwrapping
var number: Int? = nil
print(number) // 결과: nil
number = 123
print(number) // 결과: Optional(123)
// Forced Unwrapping (강제 추출)
// optionalExpression!
print(number!) // 결과: 123
number = nil
// print(number!) - 에러: 값이 없는 경우에는 강제 추출이 불가능함
// 좀 더 안전하게 확인
if number != nil {
print(number!)
}
number = 123
let before = number
let after = number! // 강제 추출해 값을 전달하기 때문에 Non-Optional 타입으로 전달
type(of: before) // 결과: Optional<Int>.Type
type(of: after) // 결과: Int.Type
2. Optional Binding
위 글에서 강제 추출(Forced Unwrapping)을 통해 Optional 타입의 값에 접근했는데 이 경우 값이 nil 이면 에러가 발생합니다. 따라서 값을 더 안전하게 추출하는 방식인 Optional Binding 에 대해 알아보겠습니다.
Optional Binding 의 형태는 다음과 같습니다.
if let name: Type = OptionalExpression {
statements
}
while let name: Type = OptionalExpression {
statements
}
guard let name: Type = OptionalExpression else {
statements
}
OptionalExpression 을 평가해서 값이 리턴이 되면 Unwrapping 되어서 상수 (또는 변수)에 저장이 되고 이 상태가 바인딩에 성공한 상태입니다. 이때 if 문이라면 if 문이 실행이 되고, while 문이라면 반복이 되고, guard 문의 경우 그 다음 문장이 실행됩니다. OptionalExpression 을 평가했을 때 nil 이 리턴이 되면 실패한 상태가 되며, if 문과 while 문의 경우 그 다음 문장이 실행되고 guard 문은 else 문이 실행됩니다.
var num: Int? = nil
if let num = num {
print(num)
type(of: num)
} else {
print("Empty")
}
// 결과: Empty
var str: String? = "str"
guard let str = str else {
fatalError()
}
str
type(of: str)
// 결과: str
// 결과: String.Type
num = 123
if var num = num {
num = 456
print(num)
}
// 결과: 456
let a: Int? = 12
let b: String? = "str"
// 바인딩이 모두 성공하고 조건 또한 true 인 경우에 if 문이 실행됨.
if let num = a, let str = b, str.count > 5 {
print("All success")
}
3. Implicitly Unwrapped Optionals
특정 조건에서 강제 추출되는 Optional 타입을 의미하며 iOS 앱 개발 시 이를 사용하는 경우는 2가지가 있습니다.
1. Outlet 을 연결할 때
2. API 에서 IUO 를 리턴하는 경우
하지만, 2가지 경우에도 그냥 Optional과 Optional Binding 을 사용하면 되기 때문에 자주 이 문법을 사용하지는 않습니다.
let num: Int! = 12
// 형식 추론을 사용하는 경우에는 자동으로 추출되지 않음
let a = num
type(of: a) // 결과: Optional<Int>.Type
// IUO 는 Non-Optional 타입으로 처리되어야 할 때 자동으로 Unwrapping 됨
let b: Int = num
type(of: b) // 결과: Int.Type
let number: Int! = nil
// IUO 는 값을 강제로 자동 추출하기 때문에 nil 이 저장된 상태에서는 에러가 발생
// let c: Int = number - 에러
4. Nil-Coalescing Operator
값이 저장되어 있는지 확인하는 코드와 값을 추출하는 코드를 직접 작성하지 않아도 값을 안전하게 추출하게 도와주는 연산자입니다.
형태는 다음과 같습니다.
OptionalExpression ?? Expression
OptionalExpression 을 평가해서 값이 있으면 값을 Unwrapping 해 리턴하고 그 값을 연산에 사용하게 됩니다. 값이 없는 경우에는 오른쪽 피연산자를 평가해 그 값을 그냥 사용하는 방식입니다. 주로 오른쪽 피연산자에는 값이 없을 때 사용할 기본값을 지정하게 되며 오른쪽 피연산자에서는 side effect 를 발생 시키는 코드를 쓰면 안됩니다.
var msg = ""
var input: String? = "Swift"
if let inputName = input {
msg = "Hello, " + inputName
} else {
msg = "Hello, Stranger"
}
print(msg)
var str = "Hello, " + (input != nil ? input! : "Stranger")
print(str)
str = "Hello, " + (input ?? "Stranger")
print(str)
// 결과는 3개 모두 Hello, Swift
5. Optional Chaining
Optional 을 연달아서 호출하는 것으로 특징을 살펴보면 다음과 같습니다.
1. Optional Chaining 의 결과는 항상 Optional 입니다.
2. Optional Chaining 에 포함된 표현식 중에서 하나라도 nil 을 리턴한다면 이어지는 표현식을 평가하지 않고 nil 을 리턴합니다.
struct Contects {
var email: [String: String]
var address: String
}
struct Person {
var name: String
var contacts: Contects
init(name: String, email: String) {
self.name = name
contacts = Contects(email: ["home": email], address: "Seoul")
}
}
var p = Person(name: "James", email: "swift@example.com")
let a = p.contacts.address
type(of: a) // 결과: String.Type
var optionalP: Person? = Person(name: "James", email: "swift@example.com")
// optionalP 에 인스턴스가 저장되어 있다면, 이어지는 contacts 속성에 접근하고
// 반대로 nil 이 저장되어 있다면, nil 을 리턴하고 바로 종료
let b = optionalP?.contacts.address
type(of: b) // 결과: Optional<String>.Type (값은 Seoul)
optionalP = nil
let c = optionalP?.contacts.address
type(of: c) // 결과: Optional<String>.Type (값은 nil)
Optional Chaining 의 리턴 형식은 마지막 표현식에 따라서 결정됩니다.
struct Contects {
var email: [String: String]?
var address: String?
func printAddress() {
return print(address ?? "no address")
}
}
struct Person {
var name: String
var contacts: Contects?
init(name: String, email: String) {
self.name = name
contacts = Contects(email: ["home": email], address: "Seoul")
}
func getContacts() -> Contects? {
return contacts
}
}
var p = Person(name: "James", email: "swift@example.com")
let a = p.contacts?.address
type(of: a) // 결과: Optional<String>.Type
var optionalP: Person? = Person(name: "James", email: "swift@example.com")
let b = optionalP?.contacts?.address
type(of: b) // 결과: Optional<String>.Type
optionalP = nil
let c = optionalP?.contacts?.address
type(of: c) // 결과: Optional<String>.Type
/*
-----------------------------------------------------------------------------
*/
// Optional 형식으로 선언되어 있는 값을 통해서 속성이나 메소드를 호출할 때는 ?를 반드시 붙여야 함
// p.contacts?.address.count - 에러
p.contacts?.address?.count // 결과: 5
// 메소드가 Optional 타입을 리턴하고 이 타입을 통해 다른 멤버에 접근할 때는 항상 () 뒤에 ? 추가
p.getContacts()?.address // 결과: Seoul
/*
-----------------------------------------------------------------------------
*/
// 함수 자체가 Optional 인 경우 함수 이름 뒤에 (즉, () 앞에) ? 를 붙임
let f: (() -> Contects?)? = p.getContacts
f?()?.address // 결과: Seoul
// () = void 값을 리턴하지 않음
let d = p.getContacts()?.printAddress()
type(of: d) // 결과: Optional<()>.Type
// 이를 통해 함수가 실행되었는지 아닌지를 파악할 수 있음
if p.getContacts()?.printAddress() != nil {
}
if let _ = p.getContacts()?.printAddress() {
// printAddress() 함수가 호출된 경우에 바인딩이 성공해서 실행
}
/*
-----------------------------------------------------------------------------
*/
let e = p.contacts?.email?["home"] // 결과: swift@example.com
p.contacts?.email?["home"]?.count // 결과: 17
// 최종 속성까지 접근했다면 값을 저장하고 중간에 끝나면 변화가 없음
p.contacts?.address = "Daegu" // 결과: ()
p.contacts?.address // 결과: Daegu
optionalP?.contacts?.address = "Daegu" // 결과: nil
optionalP?.contacts?.address // 결과: nil
6. Optional Pattern
let a: Int? = 0
let b: Optional<Int> = 0
// 축약형
if a == nil {
}
// 위와 같은 enum pattern
if a == .none {
}
// 축약형
if a == 0 {
}
// 위와 같은 enum pattern
if a == .some(0) {
}
/*
----------------------------------------
*/
// 축약형
if let x = a {
print(x) // 결과: 0
}
// 위와 같은 enum pattern
if case .some(let x) = a {
print(x) // 결과: 0
}
// 위와 같은 optional pattern
if case let x? = a {
print(x) // 결과: 0
}
/*
----------------------------------------
*/
let list: [Int?] = [0, nil, nil, 3, nil, 5]
for item in list {
guard let x = item else { continue }
print(x) // 결과: 0, 3, 5
}
for case let x? in list {
print(x) // 결과: 0, 3, 5
}
같은 기능의 코드를 더 간략하게 쓸 수 있다는 장점이 있습니다.
지금까지 Swift 의 Optionals 에 대해 알아보았습니다.
'swift > 문법' 카테고리의 다른 글
09. Swift 함수 - Closures (0) | 2020.10.17 |
---|---|
08. Swift 문법 - Functions (0) | 2020.10.17 |
06. Swift 문법 - Control Transfer Statements, Labeled Statements (0) | 2020.10.12 |
05. Swift 문법 - Loop Statements (0) | 2020.10.11 |
04. Swift 문법 - Conditional Statements (0) | 2020.10.11 |
- Total
- Today
- Yesterday
- 순열
- 소수
- SWIFT
- Combination
- 조합
- 수학
- ECR
- DFS
- Algorithm
- CodeCommit
- Dynamic Programming
- ionic
- sort
- permutation
- 프로그래머스
- string
- search
- CodePipeline
- CodeDeploy
- programmers
- Baekjoon
- map
- AWS
- java
- cloudfront
- 에라토스테네스의 체
- EC2
- array
- BFS
- spring
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |