Skip to content
This repository has been archived by the owner on Sep 8, 2023. It is now read-only.

Latest commit

ย 

History

History
486 lines (379 loc) ยท 20.2 KB

2019-04-29-optional-throws-result-async-await.md

File metadata and controls

486 lines (379 loc) ยท 20.2 KB
title author translator category excerpt status
Optional, throws, Result, ๊ทธ๋ฆฌ๊ณ  async/await
jemmons
๊น€ํ•„๊ถŒ
Swift
์Šค์œ„ํ”„ํŠธ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์˜ ๊ณผ๊ฑฐ, ํ˜„์žฌ, ๋ฏธ๋ž˜.
swift
5.0

Swift 1 ์‹œ์ ˆ์—” ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ๊ทธ๋ ‡๊ฒŒ ๋งŽ์ง€ ์•Š์•˜์ง€๋งŒ Optional ์€ ๊ทธ๋•Œ๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ์ •๋ง ๋ฉ‹์กŒ์Šต๋‹ˆ๋‹ค! ๋„(null) ํ™•์ธ์„ ๋ช…์‹œ์ ์œผ๋กœ ๋งŒ๋“ฆ์œผ๋กœ์จ, ํ•จ์ˆ˜์—์„œ nil์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ–‰๋™์ด ๋œ ๊บผ๋ ค์ง€๊ณ  ์–ธ์–ด์˜ ๊ธฐ๋Šฅ์ฒ˜๋Ÿผ ๋Š๊ปด์ง€๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ Keychain ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์–ด๋– ํ•œ ์—๋Ÿฌ๋“  nil์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์–ด๋ ต์ง€ ์•Š๊ฒŒ ๋งŒ๋‚˜๋ณผ ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ์ฃ .

func keychainData(service: String) -> Data? {
  let query: NSDictionary = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: service,
    kSecReturnData: true
  ]
  var ref: CFTypeRef? = nil

  switch SecItemCopyMatching(query, &ref) {
  case errSecSuccess:
    return ref as? Data
  default:
    return nil
  }
}

์ด ์ฝ”๋“œ์—์„  ์ฟผ๋ฆฌ๋ฅผ ์„ค์ •ํ–ˆ๊ณ , SecItemCopyMatching์— ๋นˆ inout ์ฐธ์กฐ๋ฅผ ๋„˜๊ฒผ์œผ๋ฉฐ, ๋ฐ˜ํ™˜๋œ ์Šคํ…Œ์ดํ„ฐ์Šค ์ฝ”๋“œ์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ, ์ œ๋Œ€๋กœ ๋œ ์ฐธ์กฐ๋ฅผ ๋ฐ์ดํ„ฐ๋กœ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์—๋Ÿฌ๋ฅผ nil๋กœ ๋ฐ˜ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ˜ธ์ถœํ•˜๋Š” ๊ณณ์—์„œ ์˜ต์…”๋„์„ ๋ฒ—๊ฒจ๋‚ธ ๊ฒฐ๊ณผ๋ฅผ ์•Œ๋ ค์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

if let myData = keychainData(service: "My Service") {
  <#do something with myData...#>
} else {
  fatalError("Something went wrong with... something?")
}

Getting Results

๊ฒฐ๊ณผ๊ฐ€ ์Œ์œผ๋กœ ๋‚˜์˜ค๋Š” ๊ฒฝ์šฐ์—” ์œ„์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ์šฐ์•„ํ•˜๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์น˜๋ช…์ ์ธ ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ต์…”๋„์˜ ์†์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Optional์€ ๊ทธ์ € ์–ด๋–ค ๊ฐ’์„ ๊ฐ์‹ธ๊ณ  ์žˆ๊ฑฐ๋‚˜ ์•„๋ฌด๊ฒƒ๋„ ์—†๋Š” ๊ฒƒ์˜ enum์ž…๋‹ˆ๋‹ค.

enum Optional<Wrapped> {
  case some(Wrapped)
  case none
}

๋ชจ๋“  ์ผ์ด ์ž˜ ํ’€๋ฆฌ๋Š” ๊ฒฝ์šฐ์—” ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ๋„๊ตฌ์—์„œ๋„ ๊ทธ์ € ๊ทธ์— ๋งž๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ I/O ์ž‘์—…์—์„œ๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ด์œ ๋กœ ์ผ์ด ํ‹€์–ด์งˆ ์ˆ˜ ์žˆ๋Š”๋ฐ, Optional์€ ์ผ์ด ํ‹€์–ด์กŒ๋‹ค๋Š” ๊ฒƒ๋งŒ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์— ์šฐ๋ฆฌ๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ๊ทธ์ € .none์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. (์˜ˆ๋ฅผ ๋“ค์ž๋ฉด, SecItemCopyMatching์€ ์•„์ฃผ ์•„์ฃผ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์ผ์ด ํ‹€์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค)

๊ทธ๋Ÿฌ๋ฉด ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์€ ์ผ์ด ํ‹€์–ด์ง„ ์ด์œ ๋ฅผ ์•Œ๊ณ  ์‹ถ์–ด๋„ ๊ทธ์ € ๋นˆ .none ๊ฐ’๋งŒ ๋ฐ›๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ์ตœ์•…์˜ ๊ฒฝ์šฐ๊ฐ€ ยฏ\_(ใƒ„)_/ยฏ๊ฐ™์€ ํ•˜๋‚˜์˜ ์ด์œ ๋กœ ํ•ฉ์ณ์ง€๋ฉด ๊ฐ•๋ ฅํ•œ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๋งŒ๋“ค๊ธฐ๋Š” ์ ์  ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์€ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

ํ•œ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์€ ์–ธ์–ด ๋‹จ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ธ ํ•จ์ˆ˜๊ฐ€ ์—๋Ÿฌ์™€ ๋ฐ˜ํ™˜ ๊ฐ’๊นŒ์ง€ ๋ฐœ์ƒํ•˜๋„๋ก(throws) ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ๋ฐฉ๋ฒ•์ด ๋ฐ”๋กœ Swift 2์—์„œ ์ถ”๊ฐ€๋œ throws/throw์™€ do/catch ๋ฌธ๋ฒ•์ž…๋‹ˆ๋‹ค.

Optional์˜ ์ฆ๋ช…์— ๋Œ€ํ•ด ์กฐ๊ธˆ ๋” ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค. Optional์€ ๊ฐ’ ๋˜๋Š” nil์„ ๊ฐ€์ง€๋Š”๋ฐ ์ด nil์˜ ์—๋Ÿฌ ํ‘œํ˜„๋ ฅ์ด ๋ถ€์กฑํ•œ ๊ฒƒ์ด ๋ฌธ์ œ๋ผ๋ฉด, ํ•ด๋‹น ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๊นŒ์ง€ ๋“ค์–ด์žˆ๋Š” ์—๋Ÿฌ์™€ ๊ธฐ์กด๊ณผ ๋˜‘๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง€๋Š” ์ƒˆ๋กœ์šด Optional์€ ์–ด๋–จ๊นŒ์š”?

๋ฐ”๋กœ ๊ทธ๊ฒ๋‹ˆ๋‹ค! ๊ทธ ์ƒˆ๋กœ์šด Optional์€ ์ƒˆ๋กœ์šด ์ด๋ฆ„์„ ๋ฐ›์•„์„œ Result ํƒ€์ž…์œผ๋กœ ์„ธ์ƒ์— ๋‚˜์™”์Šต๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ Swift 5 ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ๋„ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค!

enum Result<Success, Failure: Error> {
  case success(Success)
  case failure(Failure)
}

Result๋Š” ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ์—” ๊ฐ’์„, ์‹คํŒจํ•œ ๊ฒฝ์šฐ์—” ์—๋Ÿฌ๋ฅผ ๊ฐ€์ง€๋Š” ํƒ€์ž…์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์œ„์—์„œ ๋งŒ๋“ค์—ˆ๋˜ ํ‚ค์ฒด์ธ ์ฝ”๋“œ๋ฅผ ์กฐ๊ธˆ ๋” ๋ฐœ์ „์‹œ์ผœ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋จผ์ € ์ปค์Šคํ…€ Error ํƒ€์ž…์„ ์ •์˜ํ•ด์„œ nil์„ ํ’๋ถ€ํ•˜๊ฒŒ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ญ๋‹ˆ๋‹ค.

enum KeychainError: Error {
  case notData
  case notFound(name: String)
  case ioWentBad
  <#...#>
}

๋‹ค์Œ์€ keychainData์˜ ์ •์˜์— ์žˆ๋Š” Data? ๋Œ€์‹ ์— Result<Data, Error>๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ผ์ด ํ‹€์–ด์ง€์ง€ ์•Š๊ณ  ๋˜‘๋ฐ”๋กœ ์‹คํ–‰๋œ๋‹ค๋ฉด .success์˜ ์—ฐ๊ด€ ๊ฐ’๋ฅผ ๋ฐ›๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. SecItemCopyMatching์˜ ๋งŽ๊ณ  ๋‹ค์–‘ํ•œ ์žฌ์•™์ด ๋“ค์ด๋‹ฅ์นœ๋‹ค๋ฉด ์–ด๋–จ๊นŒ์š”? ์ด์   nil์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ ๋Œ€์‹ ์— ๊ฐœ๋ณ„ ์—๋Ÿฌ๋“ค์ด .failure์— ๊ฐ์‹ธ์ ธ์„œ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ๋ณด๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

func keychainData(service: String) -> Result<Data, Error> {
  let query: NSDictionary = [...]
  var ref: CFTypeRef? = nil

  switch SecItemCopyMatching(query, &ref) {
  case errSecSuccess:
    guard let data = ref as? Data else {
      return .failure(KeychainError.notData)
    }
    return .success(data)
  case errSecItemNotFound:
    return .failure(KeychainError.notFound(name: service))
  case errSecIO:
    return .failure(KeychainError.ioWentBad)
  <#...#>
  }
}

์ด์ œ ํ˜ธ์ถœ ๋ถ€๋ถ„์—์„œ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๊ฐ€ ๋งŽ์•„์กŒ๋„ค์š”! ๊ทธ๋Ÿฌ๋ฉด switch๋ฌธ์„ ๋ฐ˜ํ™˜๋œ ๊ฐ’์— ์‚ฌ์šฉํ•ด์„œ ์„ฑ๊ณต๊ณผ ์‹คํŒจ ๋ชจ๋‘ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋‹ค๋ค„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

switch keychainData(service: "My Service") {
case .success(let data):
  <#do something with data...#>
case .failure(KeychainError.notFound(let name)):
  print(""\(name)" not found in keychain.")
case .failure(KeychainError.io):
  print("Error reading from the keychain.")
case .failure(KeychainError.notData):
  print("Keychain is broken.")
<#...#>
}

๋ชจ๋“  ๊ฒฝ์šฐ๋ฅผ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ Result๋Š” Optional์˜ ์ƒ์œ„ ํ˜ธํ™˜์ด๋ผ๊ณ ๋„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ป๊ฒŒ ์ด ๊ธฐ๋Šฅ์ด ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋“ค์–ด๊ฐ€๋Š” ๋ฐ 5๋…„์ด๋‚˜ ๊ฑธ๋ ธ์„๊นŒ์š”?

Three's a Crowd

์•„์‰ฝ๊ฒŒ๋„ Result ๋˜ํ•œ ์ €์ฃผ๋ฅผ ๋ฐ›์•„ ์น˜๋ช…์ ์ธ ๋‹จ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€๋Š” ํ•˜๋‚˜์˜ ํ•จ์ˆ˜์— ํ•˜๋‚˜์˜ ํ˜ธ์ถœ๋งŒ ์žˆ์–ด์„œ ๋‚˜์˜ค์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. Result๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋‘ ๊ฐœ ๋” ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

func makeAvatar(from user: Data) -> Result<UIImage, Error> {
  <#Return avatar made from user's initials...#>
  <#or return failure...#>
}

func save(image: UIImage) -> Result<Void, Error> {
  <#Save image and return success...#>
  <#or returns failure...#>
}

{% info %} save(image:)์˜ ๋ฐ˜ํ™˜ ํƒ€์ž… ์ค‘ ์„ฑ๊ณตํ•œ ํƒ€์ž…์ด Void์ธ ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. ์ƒ๊ฐํ•ด๋ณด๋ฉด ๋‹น์—ฐํ•˜์ง€๋งŒ ์„ฑ๊ณตํ•  ๋•Œ๋งˆ๋‹ค ํŠน์ •ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ์ € ์„ฑ๊ณต ์—ฌ๋ถ€๋งŒ ์•„๋Š” ๊ฒƒ์ด ์ถฉ๋ถ„ํ•  ๋•Œ๋„ ์žˆ๊ฑฐ๋“ ์š”. {% endinfo %}

์œ„ ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด, ์ฒซ ๋ฒˆ์งธ ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์•„๋ฐ”ํƒ€๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ฒƒ์ด๊ณ , ๋‘ ๋ฒˆ์งธ ํ•จ์ˆ˜๋Š” ๋””์Šคํฌ์— ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ตฌํ˜„ ๋ฐฉ์‹์€ ์šฐ๋ฆฌ์˜ ๋ชฉ์ ์— ๊ทธ๋ ‡๊ฒŒ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ์ € ๊ทธ๋“ค์ด Result ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ๊ฒƒ๋งŒ ์•Œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ด์ œ๋Š” ํ‚ค์ฒด์ธ์—์„œ ๊ฐ€์ ธ์˜จ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์•„๋ฐ”ํƒ€๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์•„๋ฐ”ํƒ€๋ฅผ ๋””์Šคํฌ์— ์ €์žฅํ•˜๋Š” ์ž‘์—…์„ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก  ๊ทธ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋“ค๋„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•ด์•ผ๊ฒ ์ฃ ?

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹œ๋„ํ•ด ๋ณผ ์ˆ˜ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค.

switch keychainData(service: "UserData") {
case .success(let userData):

  switch makeAvatar(from: userData) {
  case .success(let avatar):

    switch save(image: avatar) {
    case .success:
      break

    case .failure(FileSystemError.readOnly):
      print("Can't write to disk.")

    <#...#>
    }

  case .failure(AvatarError.invalidUserFormat):
    print("Unable to generate avatar from given user.")

  <#...#>
  }

case .failure(KeychainError.notFound(let name)):
  print(""\(name)" not found in keychain.")

<#...#>
}

์ด๊ฒƒ ๋ณด์„ธ์š”. ๋‘ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ–ˆ์„ ๋ฟ์ธ๋ฐ ์ค‘์ฒฉ๋ฌธ์ด ์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์•„์กŒ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—” ์—๋Ÿฌ ํ•ธ๋“ค๋ง์ด ์Šฌํ”„๊ณ  ๋ณต์žกํ•ด์ง‘๋‹ˆ๋‹ค

Falling Flat

๋‹คํ–‰ํžˆ๋„ Optional๊ณผ ๊ฐ™์ด Result ๋‚ด๋ถ€์— flatMap์ด ๊ตฌํ˜„๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. Result์— flatMap์ด ์ ์šฉ๋˜๋ฉด .success์˜ ๊ฒฝ์šฐ์—” ์ฃผ์–ด์ง„ ๊ฐ’์„ ํ•ฉ์ณ์„œ ์ƒˆ๋กœ์šด Result๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ .failure์˜ ๊ฒฝ์šฐ์—” flatMap์ด ์ˆ˜์ •์—†์ด .failure์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

{% info %} flatMap๊ณผ ๊ฐ™์€ ๊ตฌํ˜„๋“ค์„ ๋•Œ๋•Œ๋กœ ๋ชจ๋‚˜๋“œ๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋ชจ๋‚˜๋“œ๊ฐ€ ๋ชจ๋‘ ๊ณตํ†ต๋œ ์†์„ฑ์„ ๊ณต์œ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•„๋Š”๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค. ๋‹จ์–ด ์ž์ฒด๊ฐ€ ์นœ์ˆ™ํ•˜์ง€ ์•Š๊ณ  ๋ฌด์„œ์šธ ์ˆ˜ ์žˆ์ง€๋งŒ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜ ์ดํ•ดํ•ด์•ผ ํ•  ๊ฒƒ์€ flatMap ๋‹จ ํ•˜๋‚˜๋‹ˆ๊นŒ์š”. {% endinfo %} {% warning %} Swift 4.1์—์„œ flatMap์ด compactMap์œผ๋กœ ๋Œ€์ฒด๋˜์—ˆ๋‹ค๋Š” ์˜คํ•ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค! Sequence์—์„œ Optional ์š”์†Œ๋ฅผ flatMapํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ํŠน๋ณ„ํ•œ ์ผ€์ด์Šค๋งŒ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. {% endwarning %}

์ด๋Ÿฌํ•œ ๊ทœ์น™์„ ์ง€ํ‚ค๋ฉฐ ์—๋Ÿฌ๋ฅผ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ ๋‹จ๊ณ„์˜ .failure๋ฅผ ์ƒ๊ฐํ•˜์ง€ ์•Š๊ณ  flatMap์„ ์‚ฌ์šฉํ•ด์„œ ์—ฐ์‚ฐ์„ ํ•ฉ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์€ ์šฐ๋ฆฌ์˜ ์ฝ”๋“œ์— ์ค‘์ฒฉ์„ ์ตœ์†Œํ™”ํ•ด์ฃผ๊ณ  ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ์—ฐ์‚ฐ์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

let result = keychainData(service: "UserData")
             .flatMap(makeAvatar)
             .flatMap(save)

switch result {
case .success:
  break

case .failure(KeychainError.notFound(let name)):
  print(""\(name)" not found in keychain.")
case .failure(AvatarError.invalidUserFormat):
  print("Unable to generate avatar from given user.")
case .failure(FileSystemError.readOnly):
  print("Can't write to disk.")
<#...#>
}

์ฝ”๋“œ ํ’ˆ์งˆ์ด ํ–ฅ์ƒ๋๋‹ค๋Š” ๊ฒƒ์—” ์˜์‹ฌ์˜ ์—ฌ์ง€๊ฐ€ ์—†๋„ค์š”. ํ•˜์ง€๋งŒ ์ด๋Š” ์ฝ”๋“œ๋ฅผ ์ฝ๋Š” ์‚ฌ๋žŒ์ด .flatMap์„ ์•Œ์•„์•ผ ํ•ด์„œ ๋œ ์ง๊ด€์ ์œผ๋กœ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

{% warning %} ์ง€๊ธˆ๊นŒ์ง€์˜ ๋‚ด์šฉ์€ ์™„๋ฒฝํ•œ ๊ฒฐํ•ฉ์„ฑ(Composability)์˜ ์ตœ์ ํ™”๋œ ์‹œ๋‚˜๋ฆฌ์˜ค์ž…๋‹ˆ๋‹ค. (์ฒซ ์—ฐ์‚ฐ์˜ ์ถœ๋ ฅ์ด ๋‘ ๋ฒˆ์งธ ์—ฐ์‚ฐ์˜ ์ž…๋ ฅ์ด ๋˜๊ณ , ๋‘ ๋ฒˆ์งธ ์—ฐ์‚ฐ์˜ ์ถœ๋ ฅ์ด ์„ธ ๋ฒˆ์งธ ์—ฐ์‚ฐ์˜ ์ž…๋ ฅ์ด ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ด์–ด์ง€๋Š” ์†์„ฑ) ๋งŒ์•ฝ ์—ฐ์‚ฐ์ž๊ฐ€ ์•„๋ฌด๊ฒƒ๋„ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค๋ฉด ์–ด๋–จ๊นŒ์š”? ์•„๋‹ˆ๋ฉด ํ•˜๋‚˜ ์ด์ƒ์˜ ๊ฐ’์„ ํ•„์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค๋ฉด ์–ด๋–จ๊นŒ์š”? ๋˜๋Š” ๊ฐ ์—ฐ์‚ฐ์ด ์ž…๋ ฅ์œผ๋กœ ๋ฐ›๋Š” ๊ฐ’๊ณผ ์ถœ๋ ฅ์œผ๋กœ ๋‚ด๋ณด๋‚ด๋Š” ๊ฐ’๋“ค์ด ์„œ๋กœ ํƒ€์ž…์ด ๋‹ค๋ฅธ ๊ฒฝ์šฐ์—” ์–ด๋–จ๊นŒ์š”? ์šฐ์•„ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์ง€๋งŒ ์ด๋Ÿฐ ๊ฒฝ์šฐ์— flatMap์„ ํ•˜๋ฉด ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. {% endwarning %}

๋น„๊ต๋ฅผ ์œ„ํ•ด ์•ž์„œ ์ง  ์ฝ”๋“œ๋ฅผ Swift 2์— ์ถ”๊ฐ€๋œ do/catch ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

do {
  let userData = try keychainData(service: "UserData")
  let avatar = try makeAvatar(from: userData)
  try save(image: avatar)

} catch KeychainError.notFound(let name) {
  print(""\(name)" not found in keychain.")

} catch AvatarError.invalidUserFormat {
  print("Not enough memory to create avatar.")

} catch FileSystemError.readOnly {
  print("Could not save avatar to read-only media.")
} <#...#>

์ฒซ ๋ฒˆ์งธ๋กœ ์ƒ๊ฐํ•ด์•ผ ํ•  ๋‚ด์šฉ์€ ๋‘ ์ฝ”๋“œ๊ฐ€ ์–ผ๋งˆ๋‚˜ ๋น„์Šทํ•œ์ง€ ์‚ดํŽด๋ณด๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์œ„์ชฝ์— ์—ฐ์‚ฐํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ๊ณ  ์•„๋ž˜์ชฝ์— ์—๋Ÿฌ ํ•ธ๋“ค๋งํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋Š” ๊ฒƒ์ด ๋น„์Šทํ•˜๋„ค์š”.

{% info %} ๋‘ ์ฝ”๋“œ๊ฐ€ ๋น„์Šทํ•˜๊ฒŒ ์ƒ๊ธด ๊ฒƒ์€ ์šฐ์—ฐ์ด ์•„๋‹™๋‹ˆ๋‹ค. Swift์˜ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์˜ ๋Œ€๋ถ€๋ถ„์ด Result์™€ ๋น„์Šทํ•œ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ๋ฒ—๊ฒจ๋‚ด๋Š” ์ผ์„ ์ข€ ๋” ์‰ฝ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ณ„์† ์‚ดํŽด๋ณด์‹œ์ฃ ... {% endinfo %}

Result ๋ฒ„์ „์€ flatMap์„ ํ†ตํ•ด์„œ ์—ฐ์‚ฐ์ž๋“ค๋ผ๋ฆฌ ์—ฐ๊ฒฐ์ด ๋ผ ์žˆ๋Š” ๋ฐ˜๋ฉด์— do/catch ์ฝ”๋“œ๋Š” ์—๋Ÿฌ ํ•ธ๋“ค๋ง์ด ์ผ์–ด๋‚˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด try๋ฅผ ๊ณ„์† ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. Result ๋ฒ„์ „์€ ๋‚ด๋ถ€ ์—ด๊ฑฐํ˜•์„ ์•Œ์•„์•ผ ํ•˜๊ณ  ๊ทธ๋ฅผ ํ†ตํ•ด switch๋ฌธ์„ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ์ง€๋งŒ do/catch ๋ฒ„์ „์€ ์—๋Ÿฌ ์ž์ฒด์— ๋” ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

์ƒˆ๋กœ ์ƒ๊ธด ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๋ฌธ๋ฒ•์€ Result์™€ ๊ด€๋ จ๋œ ๋ณต์žกํ•œ ๋‚ด์šฉ์„ ํšจ๊ณผ์ ์ด๊ฒŒ ์—†์• ๊ธด ํ•˜์ง€๋งŒ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์—ด๊ฑฐํ˜•, ์—ฐ๊ด€ ๊ฐ’, ์ œ๋„ค๋ฆญ, flatMap, ๋ชจ๋‚˜๋“œ ๋“ฑ์„ ํ•™์Šตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค... Swift 2์— ์ถ”๊ฐ€๋œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๋ฐฉ๋ฒ•์€ Result์™€ ๊ทธ ์™ธ๋“ค์„ ํ•™์Šตํ•˜์ง€ ์•Š์•„๋„ ์ž˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ง€๋‚œ 5๋…„๊ฐ„ ๊ทธ๊ฒƒ๋“ค์„ ๋‹ค ์ตํžˆ์…จ๋‹ค๋ฉด ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์„ ์ด์œ ๊ฐ€ ์—†๊ฒ ์ฃ ?

Error's Ups and Downs

๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋“ค๋„ ๊ทธ๋žฌ๋“ฏ์ด do/catch ๋ฐฉ์‹์—๋„ ์น˜๋ช…์ ์ธ ๋‹จ์ ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค...

throw๋Š” ๋งˆ์น˜ ํ•œ ๋ฐฉํ–ฅ์œผ๋กœ๋งŒ ์ž‘๋™ํ•˜๋Š” return ๊ฐ™์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ํ˜ธ์ถœํ•œ ๊ณณ์—๋‹ค๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ณด๋‚ด๋Š” ๊ฒƒ(up)๋งŒ ํ•  ์ˆ˜ ์žˆ๊ณ  ๋‹ค๋ฅธ ํ•จ์ˆ˜์— ์—๋Ÿฌ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ณด๋‚ด๋Š” ๊ฒƒ(down)์€ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ "up"-only ๋ฐฉ์‹์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์•ž์„œ ์ž‘์„ฑํ•œ ํ‚ค์ฒด์ธ ๋„๊ตฌ์˜ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ์‚ดํŽด๋ณด๋ฉด ๋ฐ์ดํ„ฐ ํ˜น์€ ์—๋Ÿฌ๋ฅผ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด์„œ return๊ณผ throw๊ฐ€ ๋‚œ๋ฌดํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

func keychainData(service: String) throws -> Data {
  let query: NSDictionary = [...]
  var ref: CFTypeRef? = nil

  switch SecItemCopyMatching(query, &ref) {
  case errSecSuccess:
    guard let data = ref as? Data else {
      throw KeychainError.notData
    }
    return data
  case errSecItemNotFound:
    throw KeychainError.notFound(name: service)
  case errSecIO:
    throw KeychainError.ioWentBad
  <#...#>
  }
}

ํ•˜์ง€๋งŒ ๋งŒ์•ฝ ํ‚ค์ฒด์ธ์—์„œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋Œ€์‹ ์— ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค์—์„œ ๊ฐ€์ ธ์˜จ๋‹ค๋ฉด ์–ด๋–จ๊นŒ์š”? ๋น ๋ฅด๊ณ  ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์—ฐ๊ฒฐ์ด ์ œ๊ณต๋œ๋‹ค ํ•ด๋„ ๋„คํŠธ์›Œํฌ์ƒ์˜ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์€ ๋””์Šคํฌ์—์„œ ์ฝ์–ด์˜ค๋Š” ๊ฒƒ๊ณผ ๋น„๊ตํ•œ๋‹ค๋ฉด ๋Š๋ฆด ์ˆ˜๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค. ๋‹น์—ฐํžˆ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค๋Š” ๋™์•ˆ ์•ฑ์ด ๋ฉˆ์ถ”๋Š” ๊ฒƒ์€ ์›ํ•˜์ง€ ์•Š์œผ์‹œ๊ฒ ์ฃ ? ๊ทธ๋Ÿฌ๋‹ˆ ๋น„๋™๊ธฐ๋กœ ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ๋กœ ๋งŒ๋“ ๋Š” ๊ฒƒ์€ ํ˜ธ์ถœํ•œ ๊ณณ์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ(up)์€ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. ๋Œ€์‹ ์— ์™„๋ฃŒ ์‹œ์— ํด๋กœ์ €๋ฅผ ๋‹ค์Œ์œผ๋กœ(down) ํ˜ธ์ถœํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

func userData(for userID: String, completion: (Data) -> Void) {
  <#get data from the network#>
  // ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„ ์‹คํ–‰
  completion(myData)
}

์˜ค๋Š˜๋‚ ์˜ ๋„คํŠธ์›Œํฌ ์—ฐ์‚ฐ์€ ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ์—๋Ÿฌ๋กœ ์ธํ•ด ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๋ชจ๋“  ์—๋Ÿฌ๋ฅผ completion์— throw๋กœ ๋„˜๊ธธ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ ๊ฐ€์žฅ ์ข‹์€ ์„ ํƒ์ง€๋Š” ์—๋Ÿฌ๋ฅผ ์˜ต์…”๋„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ธฐ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

func userData(for userID: String, completion: (Data?, Error?) -> Void) {
  <# Fetch data over the network... #>
  guard myError == nil else {
    completion(nil, myError)
  }
  completion(myData, nil)
}

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ์šฐ๋ฆฌ๊ฐ€ ์˜ˆ์ƒํ–ˆ๋˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์‹คํ–‰ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ์ƒ๊ฐ๋˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค์— ๊ด€ํ•ด์„œ๋„ ์„ค๋ช…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

userData(for: "jemmons") { maybeData, maybeError in
  switch (maybeData, maybeError) {
  case let (data?, nil):
    <#do something with data...#>
  case (nil, URLError.timedOut?):
    print("Connection timed out.")
  case (nil, nil):
    fatalError("๐Ÿค”Hmm. This should never happen.")
  case (_?, _?):
    fatalError("๐Ÿ˜ฑWhat would this even mean?")
  <#...#>
  }
}

์›๋ž˜ ๊ฐ™์•˜์œผ๋ฉด "๋ฐ์ดํ„ฐ ๋˜๋Š” nil"๊ณผ "์—๋Ÿฌ ๋˜๋Š” nil"๊ฐ€ ๋’ค์„ž์ธ ๊ฒƒ์„ ๋ณด๋Š” ๋Œ€์‹ ์— ๊ทธ์ € ๋ฐ์ดํ„ฐ ๋˜๋Š” ์—๋Ÿฌ์ผ ๊ฒฝ์šฐ๋งŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์„œ ๋„์›€์ด ๋งŽ์ด ๋ฉ๋‹ˆ๋‹ค.

Stop Me If You've Heard This Oneโ€ฆ

๋ฐ์ดํ„ฐ ๋˜๋Š” ์—๋Ÿฌ์š”? ๋˜๊ฒŒ ์นœ์ˆ™ํ•œ ๊ตฌ๋„๋„ค์š”. ๊ทธ๋ ‡๋‹ค๋ฉด Result๋ฅผ ์จ๋ณด๋Š” ๊ฒƒ์€ ์–ด๋–จ๊นŒ์š”?

func userData(for userID: String, completion: (Result<Data, Error>) -> Void) {
  // ๋ชจ๋“  ๊ฒƒ์ด ์ž˜ ํ’€๋ ธ์„ ๊ฒฝ์šฐ:
  completion(.success(myData))

  // ์ผ์ด ์ž˜ ์•ˆ ํ’€๋ฆฐ ๊ฒฝ์šฐ:
  completion(.failure(myError))
}

ํ˜ธ์ถœ ๋ถ€๋ถ„์€ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค

userData(for: "jemmons") { result in
  switch (result) {
  case (.success(let data)):
    <#do something with data...#>
  case (.failure(URLError.timedOut)):
    print("Connection timed out.")
  <#...#>
}

์™€! ์ฝ”๋“œ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด Result ํƒ€์ž…์€ "ํ•จ์ˆ˜๊ฐ€ throws๋กœ ํ‘œ์‹œ๋  ๋•Œ ๋ฆฌํ„ด๋˜๋Š” ๊ฒƒ"์— ๋Œ€ํ•œ Swift์˜ ์ถ”์ƒ์ ์ธ ์•„์ด๋””์–ด๋ฅผ ๊ตฌ์ฒดํ™”ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด๋ฅผ ์ปดํ”Œ๋ฆฌ์…˜ ํ•ธ๋“ค๋Ÿฌ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ตฌ์ฒด์ ์ธ ํƒ€์ž…์œผ๋กœ ๋ฐ›๋Š” ๋น„๋™๊ธฐ ์—ฐ์‚ฐ์„ ๋‹ค๋ฃฐ ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค

{% info %} ์ถ”์ƒ์ ์ธ ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ๊ตฌ์ฒด์ ์ธ Result ์‚ฌ์ด์˜ ์ด์ค‘์„ฑ์€ ๋™์ „์˜ ์–‘ ๋ฉด์ฒ˜๋Ÿผ ์„œ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด ์•„์ฃผ ์‚ฌ์†Œํ•ฉ๋‹ˆ๋‹ค.

Result { try somethingThatThrows() }

์ด ์ฝ”๋“œ๋Š” catch ํ•  ์ˆ˜ ์žˆ๋Š” ์ถ”์ƒ์ ์ธ ๋ฌด์–ธ๊ฐ€๋ฅผ ์–ด๋”˜๊ฐ€์— ๋„˜๊ธธ ์ˆ˜ ์žˆ๋Š” ๊ตฌ์ฒด์ ์ธ result ํƒ€์ž…์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

try someResult.get()

์ด ์ฝ”๋“œ๋Š” ๊ตฌ์ฒด์ ์ธ result ํƒ€์ž…์„ catch ํ•  ์ˆ˜ ์žˆ๋Š” ์ถ”์ƒ์ ์ธ ๋ฌด์–ธ๊ฐ€๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค. {% endinfo %}

Result์˜ ํ˜•ํƒœ๋Š” Swift 2 ์ดํ›„๋กœ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์œผ๋กœ ํ•จ์ถ•๋˜์—ˆ๋˜ ๋ฐ˜๋ฉด์—, ์ด์ œ๋Š” ๊ณต์‹์ ์œผ๋กœ Swift 5 ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ฃผ๋กœ ๋น„๋™๊ธฐ ์—๋Ÿฌ๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ณณ์—์„œ ์“ฐ๋„๋ก ์ถ”๊ฐ€๋์Šต๋‹ˆ๋‹ค. (๊ณต์‹์ ์œผ๋กœ ๋ฐœํ‘œ๋˜๊ธฐ ์ „์—๋Š” ๋ช‡๋ช‡ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ทธ๋“ค๋งŒ์˜ ๋ฒ„์ „์„ ๊ฐœ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค)

์•ž์—์„œ ๋ณด์•˜๋“ฏ์ด ์˜ต์…”๋„ ๋‘ ๊ฐœ (Value?, Error?)๋ฅผ ๋„˜๊ธฐ๋Š” ๊ฒƒ๋ณด๋‹ค ๋‚ซ๋‹ค๋Š” ๊ฒƒ์€ ์˜์‹ฌํ•  ์—ฌ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์•„์ง ๋ณด์—ฌ๋“œ๋ฆฌ์ง€ ์•Š์€ ๊ฒฝ์šฐ์ธ ๋‘˜ ์ด์ƒ์˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ํ˜ธ์ถœ์—์„œ Result๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์—๋Ÿฌ๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

flatMap์€ ๋™์‹œ์— ๋ณ€ํ™˜์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์€ ๋น„๋™๊ธฐ ๊ณต๊ฐ„์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ ๋น„๋™๊ธฐ ์—ฐ์‚ฐ์„ ํ•ฉ์„ฑํ•  ๋• ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

userData(for: "jemmons") { userResult in
  switch userResult {
  case .success(let user):
    fetchAvatar(for: user) { avatarResult in

      switch avatarResult {
      case .success(let avatar):
        cloudSave(image: avatar) { saveResult in

          switch saveResult {
          case .success:
            // ๋ชจ๋‘ ์„ฑ๊ณต!

          case .failure(URLError.timedOut)
            print("Operation timed out.")
          <#...#>
        }
      }

      case .failure(AvatarError.invalidUserFormat):
        print("User not recognized.")
      <#...#>
    }
  }

  case .failure(URLError.notConnectedToInternet):
    print("No internet detected.")
  <#...#>
}

{% info %} ์‹ค์ œ๋กœ flatMap๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์˜ ํ•ธ๋“ค๋ง๋„ ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋Š” Continuation Monad๋ผ๊ณ  ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. ์ด๋Š” ์ง€๊ธˆ ์ตํžˆ๊ธฐ์—” ์ถฉ๋ถ„ํžˆ ๋ณต์žกํ•ด์„œ ์ด๊ฒƒ ํ•˜๋‚˜๋งŒ์œผ๋กœ๋„ ๋ช‡ ๊ฐœ์˜ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŒ…์ด ๋‚˜์˜ฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค. {% endinfo %}

Awaiting the Future

Swift๊ฐ€ ๋™์‹œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ์—์„œ Result ์ค‘์ฒฉ ๋ฌธ์ œ๋ฅผ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ do/catch ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•œ ๊ฒƒ์ฒ˜๋Ÿผ, ๋น„๋™๊ธฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด์„œ๋„ ๋น„์Šทํ•œ ์ œ์•ˆ์ด ๋งŽ์Šต๋‹ˆ๋‹ค.

async/await ์ œ์•ˆ์€ ๊ทธ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ์ด ์ œ์•ˆ์„ ์šฐ๋ฆฌ ์ฝ”๋“œ์— ์ ์šฉํ•ด๋ณธ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

do {
  let user = try await userData(for: "jemmons")
  let avatar = try await fetchAvatar(for: user)
  try await cloudSave(image: avatar)

} catch AvatarError.invalidUserFormat {
  print("User not recognized.")

} catch URLError.timedOut {
  print("Operation timed out.")

} catch URLError.notConnectedToInternet {
    print("No internet detected.")
} <#...#>

์—ฌ๊ธฐ๊นŒ์ง€ Swift ์—๋Ÿฌ ํ•ธ๋“ค๋ง์˜ ์–ด๋‘์šด ๊ณผ๋„๊ธฐ์— ๊ธธ์„ ๋ฐํ˜€์ค€ Result ํƒ€์ž…์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.