본문 바로가기

학습 노트/Swift (2021)

043 ~ 048. Optionals

Optionals

let num: Int
print(num)

 

결과

//error


변수와 상수는 사용하기 전 반드시 초기화해야 한다.
하지만 초깃값을 비워야 하는 경우 해결책으로

  • 0을 비었다고 사용한다.
  • 특정 문자열을 비었다고 사용한다.

을 사용하는 경우 각각

  • 0을 다른 방법으로 사용 할 수 없다.
  • 자료형을 항상 문자열로 고정 할 수는 없다.
Syntax

TypeName?
let optionalNum: Int? = nil
print(num)

 

결과

nil

nil은 c나 기타 언어의 null과 같은 역할을 한다.
Optional은 '값이 비어있음'을 표현할 수 있는 자료형이다.

 

Unwrapping

var num: Int? = nil
print(num)
num = 123
print(num)

 

결과

nil
Optional(123)

출력되는 값이 일반적인 값과 다른 것은 물론

var num: Int? = 123
var num2 = 456

print(num + num2)

 

결과

//error

unoptional 변수와 연산조차 되지 않는다.
이를 해결하기 위해 'optional(~)'의 껍질을 벗겨주어야 하는데 이를 Unwrapping이라고 한다.

Syntax

OptionalExpression!
var num: Int? = 123
var num2 = 456

print(num! + num2)

 

결과

468

정상정으로 unoptional자료형과 연산이 가능해진다.
다만 해당 방식은 forced unwrapping 방식으로 '강제'로 unwraaping 한다는 점에서 주의할 점이 있다.

var num: Int? = nil
var num2 = 456

print(num! + num2)

 

결과

//error

unwrapping 대상이 nil로 비어있을 경우 문제가 발생한다.
따라서 forced unwrapping을 사용하는 경우 다음과 같이 값이 존재하는지를 먼저 확인하는 것이 좋다.

var num: Int? = nil
var num2 = 456

if num != nil {
    print(num! + num2)
} else {
    print("dodge")
}

 

결과

dodge

실제로 코드를 작성할 때는 강제 추출 사용을 지양하는 것이 좋다.


Optional Binding

강제 추출 대신에 사용할 수 있는 안전한 추출을 Optional Binding이라고 부른다.

Syntax

if let name: Type = OptionalExpression {
    statements
}

while let name: Type = OptionalExpression {
    statements
}

guard let name: Type = OptionalExpression else {
    statements
}
var n: Int? = nil

if n != nil {
    print(n!)
} else {
    print("dodge")
}
    
if let n = n {
    print(n)
} else {
    print("dodge")
}

 

결과

dodge
dodge
var str: String? = nil
guard let str = str else {
    fatalError()
}
str

 

결과

//error

형식 추론을 통해 생략 가능하기 때문에 Type은 생략하는 경우가 많다.
또한 optional binding은 기존 변수나 상수와 이름이 같아도 재선 언 할 수 있는데,
scope와 identifier의 규칙에 의해 가장 인접한 scope의 것을 사용한다.

또한 나열하여 여러 개를 동시에 binding 할 수 있다.

let a: Int? = nil
let b: Int? = 123

if let num = a, num2 = b {
    print(b)
} else {
    print("dodge")
}

 

결과

dodge


이 경우에도 마찬가지로 나열한 optional binding이 모두 성공해야 한다.

 

Implicitly Unwrapped Optionals / IUO

특정 조건에서 자동으로 추출하는 Optional이다.

Syntax

Type!
let num: Int! = 12

let a = num
print(type(of: a))

let b: Int = num
print(type(of: b))

 

결과

Optional<Int>
Int

IUO의 특정 조건이라 함은 '형식 지정'을 의미한다.

let num: Int! = nil

let b: Int = num
print(type(of: b))

 

결과

//error

또한 IUO는 추출을 진행할 때 '강제 추출'을 진행한다.
따라서 값이 저장되지 않은 경우 error가 발생한다.

IUO는 특정 조건에서 자동으로 강제 추출한다,


Nil-Coalescing Operator

Optional이 비어있을 경우 기본으로 사용될 값을 지정한다.

//if로 구현 했을 때
var msg = ""
var input: String? = "so serious?"

if let input = input {
    msg = "why " + input
} else {
    msg = "you?"
}

print(msg)

//조건연산자로 구현하기
var str = "why " + (input != nil ? input! : "you?")
print(str)

 

결과

why so serious?
why so serious?
Syntax

A ?? B
OptionalExpression ?? Expression
input = "so serious?"
str = "why " + (input ?? "you?")
print(str)

 

결과

why so serious
input = nil
str = "why " + (input ?? "you?")
print(str)

 

결과

why you?

값을 반환할 수 있는 경우 값을 반환하여 코드를 진행한다.
값을 반환할 수 없는 경우 우항의 기본 값을 반환한다.
논리식과 동일하게 단락 평가를 진행하기 때문에 좌항이 성립하는 경우 우항은 실행되지 않기 때문에 sideeffect에 조심하자.

 

Optional Chaining

Optional Chaining의 결과는 항상 Optional이다.
Optional Chaining에 포함된 표현식 중에 하나라도 nil이 반환되면 결과는 nil이다.

//extrainfo 구조체 선언
struct ExtraInfo {
   var Organic: [String: Bool]
   var Origin: String
}

//fruit 구조체 선언
struct Fruit {
   var name: String
   var info: ExtraInfo

   init(name: String, Organic: Bool) { //extrainfo 구조체 초기화
      self.name = name
      info = ExtraInfo(Organic: ["HACCP": true], Origin: "Seoul")
   }
}

var f = Fruit(name: "Apple", Organic: true)
let fa = f.info.Origin

var Optionalf: Fruit? = Fruit(name: "Apple", Organic: true)
let fb = Optionalf.info.Origin

 

결과

//error

optional에 접근하려면 반드시 해당 instance를 추출해야 한다.

let fb = Optionalf?.info.Origin
결과

Optional("Seoul")

위와 같이 코드를 수정하면 Optionalf에 값이 저장되어 있는 경우 하위 instance에 접근한다.
하지만 값이 저장되어 있지 않은 경우 해당 단계에서 nil을 반환하고 종료한다.

Optionalf = nil
let fc = Optionalf?.info.Origin
fc
결과

Optionalf = nil

따라서 Optionalf의 값을 nil로 초기화하게 되면 이하의 instance로 접근하지 않고 nil을 반환한다.

Optional Chaining은 항상 모두 optional로 표현되진 않는다.
Optional Chaining으로 반환되는 형식은 가장 마지막 표현식이 기준이 된다.

//extrainfo 구조체 선언
struct ExtraInfo {
   var Organic: [String: Bool]
   var Origin: String
}

//fruit 구조체 선언
struct Fruit {
   var name: String
   var info: ExtraInfo?

   init(name: String, Organic: Bool) { //extrainfo 구조체 초기화
      self.name = name
      info = ExtraInfo(Organic: ["HACCP": true], Origin: "Seoul")
   }
}

let fc = Optionalf?.info.Origin
print(fc)

 

결과

//error

위와 같이 info가 속한 ExtraInfo를 Optional로 바꾸게 되면 추출하지 않았으므로 오류가 발생한다.

let fc = Optionalf?.info?.Origin
print(fc)

 

결과

Optional("Seoul")

다음과 같이 추출을 진행하면 원래대로 사용할 수 있다.

struct ExtraInfo {
   var Organic: [String: Bool]
   var Origin: String
}

struct Fruit {
   var name: String
   var info: ExtraInfo?

   init(name: String, Organic: Bool) {
      self.name = name
      info = ExtraInfo(Organic: ["HACCP": true], Origin: "Seoul")
   }
    
    func getInfo() -> ExtraInfo? {
        return info
    }
}

f.getInfo()?.Organic

 

결과

Optional(["HACCP": true])

method가 optional을 반환하고, 반환된 값을 이용하여 다른 멤버에 접근해야 할 때는 괄호 뒤에 '?'가 붙는다.

struct ExtraInfo {
    var Organic: [String: Bool]
    var Origin: String
    func printf() {
        return print(Origin ?? "unknown")
    }
}

struct Fruit {
   var name: String
   var info: ExtraInfo?

   init(name: String, Organic: Bool) {
      self.name = name
      info = ExtraInfo(Organic: ["HACCP": true], Origin: "Seoul")
   }
    
    func getInfo() -> ExtraInfo? {
        return info
    }
}

var f = Fruit(name: "Apple", Organic: true)
let g = f.getInfo()?.printf()
print(type(of: g))

 

결과

Seoul
Optional<()>

마지막 접근하는 printf()의 반환이 없어 위와 같은 자료형이 반환된다.
'( )'은 void로 반환 값이 없음을 의미한다.

//
if f.getInfo()?.printf() != nil {
    
}

//optional binding과 wildcard 활용하기
if let _ = f.getInfo()?.printf() {
    
}

 

결과

Seoul
Seoul

반환값이 없기 때문에 위와 같이 method의 호출 여부를 확인하는 코드가 필요할 수 있다.
반환이 성공하는 경우 optional void를, 실패하는 경우 nil을 반환한다.

struct ExtraInfo {
    var Organic: [String: Bool]?
    var Origin: String?
    func printf() {
        return print(Origin ?? "unknown")
    }
}

struct Fruit {
   var name: String
   var info: ExtraInfo?

   init(name: String, Organic: Bool) {
      self.name = name
      info = ExtraInfo(Organic: ["HACCP": true], Origin: "Seoul")
   }
    
    func getInfo() -> ExtraInfo? {
        return info
    }
}

let h = f.info?.Organic?["HACCP"]
print(h)

 

결과

Optional(true)

subscript에 접근하는 경우 '[ ]'앞에 '?'가 붙는다.
이 경우 value의 자료형을 채용한다.

만약 이 상태에서 다시 한번 method나 속성에 접근하게 된다면 다음과 같은 형태가 된다.

//.description은 boolean을 string으로 변환하는 method이다.
let h = f.info?.Organic?["HACCP"]?.description
print(h)

 

결과

Optional("true")

method에 접근할 때 optional로 접근하며, 이때 반환되는 값도 optional이기 때문에 다시 접근하기 위해 '[ ]'뒤에도 '?'가 붙는다.

struct ExtraInfo {
   var Organic: [String: Bool]?
   var Origin: String?
    
    func printf() {
        return print(Origin ?? "not found")
    }
}

struct Fruit {
   var name: String
   var info: ExtraInfo?

   init(name: String, Organic: Bool) {
      self.name = name
      info = ExtraInfo(Organic: ["HACCP": true], Origin: "Seoul")
   }
    
    func getInfo() -> ExtraInfo? {
        return info
    }
}

var f = Fruit(name: "Apple", Organic: true)
var Optionalf: Fruit? = nil

f.info?.Origin = "Busan"
print(f.info?.Origin)
Optionalf?.info?.Origin = "Busan"
print(Optionalf?.info?.Origin)

 

결과

Optional("Busan")
nil

Optionalf가 nil로 이후에 이어진 속성엔 접근하지 않아 Busan을 저장하지 않고, 결과적으로 nil이 출력된다.

'?'가 붙는 위치는 컴파일러가 알아서 수정 해 주니 따로 외우지 않아도 사용할 수는 있다.
다만 익숙해지는 것이 좋다.

 

Optional Pattern

let a: Int? = 0
let b: Optional<Int> = 0

if a == nil {

}

if a == .none {

}

if a == 0 {

}

if a == .some(0) {

}

if let x = a {
    print(x)
}

if case .some(let x) = 0 {
    print(x)
}

 

결과

0
0

optional pattern은 enumeration pattern의 optional 버전이다.

let a: Int? = 0
let b: Optional<Int> = 0

if case let x? = a {
    print(x)
}

 

결과

0

optional binding만 사용하는 것보다 코드가 단순해진다는 장점이 있다.

결과

0
2
3
5

코드는 guard의 continue를 포함해 6번 모두 반복하지만 print함수는 네 번 호출된다.

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

 

결과

0
2
3
5

binding에 실패하는 경우 반복 코드를 실행하지 않기 때문에 반복 횟수 자체가 줄어든다.
guard문이 사라졌기 때문에 라인도 함께 줄어든 것을 볼 수 있다.