23 April 2021

Swift에서 nil 값을 풀어서 사용할 때 if let 문을 이용하여 처리를 많이 합니다.

let a: Int? = 10

if let a = a { 
  print(a)
} else {
  print("nil")
}

사실 else를 사용하지 않을 때는 if let 문이 귀찮을때도 있기도 합니다.

Optional 타입에도 map을 이용할 수 있다는 것을 아시나요? enum 타입인 Optional은 ExpressibleByNilLiteral 프로토콜을 따르고 있기도 하고, map, flatMap이 구현되어 있습니다.

@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
...
    @inlinable public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

    @inlinable public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
...

Optional에 mapflatMap 함수를 이용하여 간결한 if let 문을 대체할 수 있습니다.

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

a.map { print("Result : \($0)") }
  ?? print("map Error")

b.flatMap { print("flatMap Error") }
  ?? print("Result : nil")

/** 
-- output --
Result : 10
Result : nil
*/

만약 map이나 flatMap에서 받은 결과를 변수에 저장해야 한다면, 해당 타입은 어떻게 될까요?

let a: String? = "10"
let b: String? = nil

let c: Int?? = a.map { Int($0) } 
let d: Int? = b.flatMap { Int($0) }

a 값을 map으로 변환할 떄, Int.init() 에서 값이 옵셔널로 반환되기 때문에 (Int?)? 형태가 됩니다. 그래서 변수 c의 타입이 Int??로 정의됩니다.

b 값을 flatMap으로 변환할 때 Int.init() 에서 값이 옵셔널이 반환되더라도 flatMap은 Int를 반환하므로 (Int)? 형태를 취합니다.. 그래서 변수 d의 타입은 Int?로 정의됩니다.

따라서 체이닝이 연속될때는 flatMap을 쓰며, map은 return이 Void 인 경우 사용하면 좋습니다.

let a: String? = "5"
let b: String? = nil

a.map { Int($0) }.map { String($0) } // Compile Error
a.flatMap { Int($0) }.map { String($0) } // Ok

func returnVoid() {
  a.flatMap { Int($0) }.flatMap { String($0)}.map { print($0) } // Ok
}