Skip to content

iOS-Udemy-Study-Group/Hello-Swift-Concurrency

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

26 Commits
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Hello-Swift-Concurrency

Udemy Study for Swift Concurrency

์Šคํ„ฐ๋”” ๋ฐฉ์‹

  • Udemy ๊ฐ•์˜๋ฅผ ๊ตฌ๋งค ํ›„ ๊ฐœ๋ณ„ ์ธ์ฆ์„ ํ•ด์•ผ ์Šคํ„ฐ๋”” ์ฐธ์—ฌ ๊ฐ€๋Šฅ
  • ๊ฐ์ž ํ•™์Šตํ•œ ๋‚ด์šฉ์„ ๊ณต์šฉ study git repository์— ๊ณต์œ ํ•˜๊ณ  ํ† ์˜ํ•˜๋ฉด์„œ ๋‚ด์šฉ ๋ณด์™„
    • ๊ฐœ์ธ์ ์œผ๋กœ ๊ณต๋ถ€ํ•œ ๋‚ด์šฉ์„ ๊ฐœ์ธํด๋”์— ๊ธฐ๋ก (fork ํ›„, ๊ฐœ์ธ ์Šคํ„ฐ๋”” ๋‚ด์šฉ ๊ธฐ๋ก ํ–ˆ๋‹ค๊ฐ€ ๊ณต์šฉ repository์— PR)
      • ๊ฐ์ž ์˜๋ฌด๊ฐ์„ ๊ฐ–๊ณ  ์Šคํ„ฐ๋””ํ•˜๊ธฐ ์œ„ํ•จ
    • ๋งค์ฃผ ๋‹ค์ˆ˜๊ฒฐ๋กœ ์Šคํ„ฐ๋”” ๋‚ ์งœ ์ง€์ • ํ›„, 2์‹œ๊ฐ„์”ฉ ์Šคํ„ฐ๋”” ์ง„ํ–‰
  • ์Šคํ„ฐ๋”” ๊ธฐ๋ก ์ •๋ฆฌ ๋ฐฉ์‹์— ๋Œ€ํ•œ ์ข‹์€ ์˜๊ฒฌ ์ž์œ ๋กญ๊ฒŒ ๊ณต์œ 
  • ์Šคํ„ฐ๋”” ์ค‘์— ์ž์œ ๋กญ๊ฒŒ ์ดํ•ดํ•œ ๋‚ด์šฉ์„ ์–˜๊ธฐํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š์€ ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ๊ณต์œ 

1์ฃผ์ฐจ ์Šคํ„ฐ๋””

2์ฃผ์ฐจ ์Šคํ„ฐ๋””

  • 4/1(์ผ), ์˜คํ›„ 1์‹œ ~ 3์‹œ
  • Section 6: Async/Await Using Continuation ~ Section 9: Download RandomImages and Quotes
  • ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป applebuddy | ChoiYS | Jae-eun | JongHoooon | Lim-YongKwan

3์ฃผ์ฐจ ์Šคํ„ฐ๋”” (์˜คํ”„๋ผ์ธ ๐Ÿ˜€)

4์ฃผ์ฐจ ์Šคํ„ฐ๋””


Concurrency

Concurrency๋ž€, ๋™์‹œ์— ๋‹ค์ˆ˜์˜ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ.

  • serialํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” Main thread์—์„œ UI Event, Downloading Images task๋“ฑ์„ ๋ชจ๋‘ ๋™์ž‘์‹œํ‚จ๋‹ค๋ฉด?โ€ฆ ๋ฉˆ์ถคํ˜„์ƒ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ => ๋งค์šฐ ๋‚˜์œ user experience๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ

  • ์ž‘์—… ํŠน์„ฑ์— ๋งž๊ฒŒ ๊ฐ๊ธฐ thread์—์„œ ๋™์ž‘ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Œ (ex) downloading images๋ฅผ main thread ๋Œ€์‹  background thread์—์„œ ๋™์ž‘)

    • DispatchQueue.global().async { let _ = try? Data(contentOf: imageURL }
  • ์ดํ›„ main thread์—์„œ UI๋ฅผ ์—…๋ฐ์ดํŠธ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ

    • DispatchQueue.main.async { // update the ui }
  • ์ด์ฒ˜๋Ÿผ GCD๋ฅผ ์ด์šฉํ•ด ์ƒํ™ฉ์— ๋”ฐ๋ผ main, background thread์—์„œ ๋น„๋™๊ธฐ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Œ

GCD

DispatchQueue(Grand Central DispatchQueue)๋Š” ๋งž์ถคํ˜• ์ž‘์—… ์‹คํ–‰์„ ์œ„ํ•œ C-๊ธฐ๋ฐ˜ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. DispatchQueue๋Š” ์ˆœ์ฐจ์ ์ด๊ฑฐ๋‚˜ ๋™์‹œ์ ์œผ๋กœ ์ž‘์—…์„ ์‹คํ–‰ํ•˜์ง€๋งŒ ์ด๋•Œ ํ•ญ์ƒ FIFO(First In First Out) ์ˆœ์„œ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. SerialQueue๋Š” ์ˆœ์ฐจ์ ์œผ๋กœ ์ž‘์—…์ด ์ง„ํ–‰๋˜๊ณ , ConcurrentQueue๋Š” ๋™์‹œ์ ์œผ๋กœ ์ž‘์—…์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ๊ทธ ์™ธ GlobalQueue(QoS), CustomQueue(Serial, Concurrent), MainQueue(Serial)๋“ฑ์„ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

DispatchQueue์˜ ํŠน์ง•

  • ์‰ฝ๊ณ  ๊ฐ„๊ฒฐํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ์ž๋™์ ์ด๊ณ  ์ „์ฒด์ ์ธ ์Šค๋ ˆ๋“œ ํ’€ ๊ด€๋ฆฌ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ์ ์ ˆํžˆ ์กฐ์œจ๋œ ์–ด์…ˆ๋ธ”๋ฆฌ์–ด์˜ ์Šคํ”ผ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์— ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. (์Šค๋ ˆ๋“œ ์Šคํƒ์ด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.)
  • ์ปค๋„์˜ ๋ถ€ํ•˜๋ฅผ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • DispatchQueue๋ฅผ ํ†ตํ•œ ์ž‘์—… ๋น„๋™๊ธฐ ์ „์†ก์€ ๋Œ€๊ธฐ์—ด์— ๊ต์ฐฉ์ƒํƒœ๋ฅผ ์•ผ๊ธฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Main Queue (serial queue)

  • Main thread๋Š” serial queue๋กœ ๋™์ž‘ํ•œ๋‹ค. ํ•˜๋‚˜์”ฉ ์ˆœ์ฐจ์ ์œผ๋กœ ์ž‘์—…์ด ์ง„ํ–‰ ๋œ๋‹ค. ํ•˜๋‚˜์˜ ์ž‘์—…์ด ์ง„ํ–‰๋˜๋Š”๋™์•ˆ ๋‹ค๋ฅธ ์ด๋ฒคํŠธ๋Š” ์ฒ˜๋ฆฌํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค.

Global Queue (Concurrent)

  • Global Queue๋Š” QoS(Quality of Service)๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • User Interactive
      • animation, event handling, updating user interface ๋“ฑ ์‚ฌ์šฉ์ž์™€ ์ง์—… ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์ž‘์—…
      • ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋งŽ์€ ๋กœ๋“œ๊ฐ€ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ๋Š” ์ž‘์—…๋“ค์€ userInteractive์—์„œ ์ฒ˜๋ฆฌํ•ด์„œ ๋ฐ”๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Œ
    • User Initiated
      • ์ €์žฅ๋œ ๋ฌธ์„œ ์—ด๊ธฐ ๋“ฑ ํด๋ฆญ ์‹œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์ฒ˜๋Ÿผ ์ฆ‰๊ฐ์ ์ธ ๊ฒฐ๊ณผ๊ฐ€ ํ•„์š”ํ•œ ์ž‘์—…,
      • userInteractive๋ณด๋‹ค๋Š” ์กฐ๊ธˆ ์˜ค๋ž˜๊ฑธ๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ ์œ ์ €๊ฐ€ ์ด๋ฅผ ์ธ์ง€ํ•˜๊ณ  ์žˆ์Œ
    • Utility
      • ๋ฐ์ดํ„ฐ ๋‹ค์šด๋กœ๋“œ ์ฒ˜๋Ÿผ ๋ณดํ†ต progress bar์™€ ํ•จ๊ป˜ ๊ธธ๊ฒŒ ์‹คํ–‰๋˜๋Š” ์ž‘์—…
    • Background
      • ๋™๊ธฐํ™” ๋ฐ ๋ฐฑ์—… ์ฒ˜๋Ÿผ ์œ ์ €๊ฐ€ ์ง์ ‘์ ์œผ๋กœ ์ธ์ง€ํ•  ํ•„์š”์„ฑ์ด ์ ์€ ์ž‘์—…
    • Default
      • ์ผ๋ฐ˜์ ์ธ ์ž‘์—…
    • Unspecified
      • ๋ช…ํ™•ํžˆ ์ง€์ •๋œ QoS๊ฐ€ ์—†์Œ

Creating a global Background Queue

DispatchQueue.global().async {
  // download the image
  
  // refresh the UI (background queue ์—์„œ UI๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๋ฉด ์•ˆ๋จ)
}

DispatchQueue.global().async {
  // download the image
  DispatchQueue.main.async {
    // refresh the UI (UI ๊ด€๋ จ ์ž‘์—…์€ Main thread์—์„œ ๋™์ž‘์‹œ์ผœ์•ผ ํ•จ)
  }
}

Creating a my Serial Queue

// Serial Queue๋Š” Concurrent Queue์™€ ๋‹ฌ๋ฆฌ ์ˆœ์ฐจ์ ์œผ๋กœ ์ž‘์—…์ด ์ง„ํ–‰๋˜๋ฏ€๋กœ ์ž‘์—… ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋œ๋‹ค.
let queue = DispatchQueue(label: "SerialQueue")

queue.async {
  // this task is executed first
}

queue.async {
  // this task is executed second
}

Creating a my Concurrent Queue

// Concurrent Queue๋Š” Serial Queue์™€ ๋‹ฌ๋ฆฌ ์ž‘์—… ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค.
let queue = DispatchQueue(label: "ConcurrentQueue", attributes: .concurrent)

queue.async {
  // ...
}

queue.async {
  // ...
}

// Tasks will start in the order they are added but they can finish in any order (์‹œ์ž‘์€ ์ˆœ์„œ๋Œ€๋กœ ์ง„ํ–‰๋˜์ง€๋งŒ, ์ž‘์—…์ด ์ข…๋ฃŒ๋˜๋Š” ์ˆœ์„œ๋Š” ๋ณด์žฅ๋˜์ง€ ์•Š์Œ)

Why is the Design Patterns important?

  • Best practices, ์‹ค์šฉ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋…ธ๋ ฅ

  • Relationships between classes and objects, ํด๋ž˜์Šค ๋“ฑ์˜ ๊ฐ์ฒด ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์ •์˜

  • Speed up development, ๊ฐœ๋ฐœ ์†๋„ ํ–ฅ์ƒ

  • Programming independent, ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋…๋ฆฝ์„ฑ

  • Flexible, reusable and maintainable, ์œตํ†ต์ ์œผ๋กœ, ์žฌ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๊ฒŒ, ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ๋”์šฑ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด

MVVM

  • Model, View, ViewModel๋กœ ๊ตฌ์„ฑ๋˜๋Š” ๋””์ž์ธํŒจํ„ด ๊ธฐ๋ฒ•
  • ViewModel์ด ๋น„์ฆˆ๋‹ˆ์Šค๋กœ์ง์„ ๊ฐ€์ ธ๊ฐ€๊ฒŒ ๋˜๋ฉฐ MVC์˜ Massive ViewController๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ  Testability์˜ ์–ด๋ ค์›€์„ ํ•ด์†Œํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ViewModel์ด ์ฃผ์š” ๋น„์ฆˆ๋‹ˆ์Šค๋กœ์ง์„ ๊ฐ–๊ณ  ์žˆ๋‹ค. ViewModel์˜ ๋ณ€ํ™”๋ฅผ View๋Š” ๊ฐ์ง€ํ•˜๊ณ  ๊ทธ์— ๋งž๊ฒŒ ๋ณ€ํ™”ํ•œ๋‹ค.
  • View๋Š” ์ด๋ฒคํŠธ๋ฅผ ViewModel์— ์ „๋‹ฌํ•˜๊ณ , ViewModel์€ ์ด๋ฒคํŠธ์— ๋งž๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
  • ViewModel์—์„œ๋Š” Constant๊ฐ’์ด๋‚˜ ๋ณต์žกํ•ด์ง€๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋ชจ๋ธ ๋“ฑ์€ Model๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌ๋œ๋‹ค. (View, Model์€ ์„œ๋กœ ์ง์ ‘์ ์œผ๋กœ ์†Œํ†ตํ• ์ผ์ด ์—†๋‹ค.)
  • why MVVM? : view๋กœ๋ถ€ํ„ฐ ๋“ค์–ด์˜ค๋Š” value์— ๋Œ€ํ•œ validation์„ ViewModel์—์„œ ํ•  ์ˆ˜ ์žˆ๋‹ค. ViewModel์€ View ๋ถ„๋ฆฌ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— View์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๊ณ  ViewModel์— ๋…๋ฆฝ์ ์ธ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์„œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์šฉ์ดํ•˜๋‹ค.

MVVM์—์„œ์˜ Web API ๋™์ž‘

  • View -> Web service -> API ์š”์ฒญ์„ ํ•˜๋Š” ๊ฒƒ์€ ๊ฐ€๋Šฅ์€ ํ•˜์ง€๋งŒ ๊ฒฐ์ฝ” ์ข‹์€ ๋กœ์ง์ด ์•„๋‹ˆ๋‹ค.
  • MVVM ํŒจํ„ด์—์„œ๋Š” View - ์ด๋ฒคํŠธ -> ViewModel -> Web service / Client -> API ์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

What is Continuation?

  • continuation์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ์กด์˜ callback closure๊ฐ€ ์žˆ๋Š” legacy ๋ฉ”์„œ๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ณ  wrappingํ•ด์„œ ์™ธ๋ถ€์—์„œ ์ฝœ๋ฒก ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ async await ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค€๋‹ค.

  • callback closure๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์—ฌ๋Ÿฌ ์‚ฌ์œ ๋กœ ๋ณ€ํ˜•ํ•˜๊ธฐ ํž˜๋“  third-party, legacy ๋ฉ”์„œ๋“œ๋ฅผ wrappingํ•ด์„œ ์™ธ๋ถ€์—์„œ async await ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ ์ž ํ• ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • withCheckedContinuation ์‚ฌ์šฉ ์˜ˆ์‹œ

func getPosts() async throws -> [Post] {
  // error๋ฅผ throwํ•  ์ผ์ด ์—†์œผ๋ฉด withCheckedContinuation์„ ์‚ฌ์šฉ
  return await withCheckedContinuation { continuation in
    // continuation์„ ํ™œ์šฉํ•˜๋ฉด callback closure๊ฐ€ ์žˆ๋Š” getPosts ๋ฉ”์„œ๋“œ๋ฅผ ์™ธ๋ถ€์—์„œ๋Š” async await ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.
		getPosts { posts in
			continuation.resume(returning: posts)
		}
	}
}

Inference of @MainActor

Global actor-qualified wrappedValue๋ฅผ ๊ฐ–๊ณ ์žˆ๋Š” propertyWrapper๋ฅผ ์ฑ„ํƒํ•œ ํ”„๋Ÿฌํผํ‹ฐ๋ฅผ ๊ฐ–๊ณ ์žˆ๋Š” ๊ตฌ์กฐ์ฒด ๋˜๋Š” ํด๋ž˜์Šค๋Š” @MainActor๋กœ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค.
์ดˆ๋ฐ˜๋ถ€ ์ƒ‰์…˜์—์„œ @StateObject์˜ ์‚ฌ์šฉ๊ณผ @MainActor์˜ ์ถ”๋ก ์— ๋Œ€ํ•œ ์˜๋ฌธ์ด ์žˆ์—ˆ๋Š”๋ฐ, ์œ„์™€๊ฐ™์€ ์ด์œ ๋กœ ์ดํ•ดํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

MainActor

Reference: https://github.com/apple/swift-evolution/blob/main/proposals/0316-global-actors.md

Section 7: Project Time: News App

News App ์ดˆ๊ธฐ์ƒํƒœ๋Š” async await, continiuation ๋“ฑ์˜ Concurrency๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค. @escaping closure ๋“ฑ์œผ๋กœ ์ฝœ๋ฐฑ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ฝœ๋ฐฑ ์ง€์˜ฅ์„ ์•ผ๊ธฐํ•˜๊ฑฐ๋‚˜, ์ฝœ๋ฐฑ ํด๋กœ์ ธ ์‹คํ–‰ ํ›„ ํŠน์ • ๋ถ„๊ธฐ return์„ ๋†“์น˜๋ฉด ๋น„์ •์ƒ ๋™์ž‘์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ์ด ์•ฑ์— async/await, continuation, mainActor ๋“ฑ์˜ ๊ฐœ๋…์„ ์ ์šฉํ•ด ๋ด…์‹œ๋‹ค!

async/await, continuation, @MainActor ๋“ฑ์˜ ๊ฐœ๋…๋“ค์€ URLSession, Notification, HealthKit, CoreData ๋“ฑ ๋‹ค์–‘ํ•œ ๊ณณ์—์„œ ํ™œ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค

Section 8: Understanding Structured Concurrency in Swift

๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป learning point : Structured Concurrency, Async Let, Task Group, Unstructured Tasks, Detached Tasks, Task Cancellation

async-let Tasks

// try await์„ ์‚ฌ์šฉํ•˜์˜€๊ธฐ์— equifaxUrl๋กœ๋ถ€ํ„ฐ ๊ฒฐ๊ณผ ๊ฐ’์„ ์ˆ˜์‹ ๋ฐ›์„๋•Œ๊นŒ์ง€ suspend ๋œ๋‹ค. ใ… ใ…  equifaxUrl ์š”์ฒญ์ด ๋๋‚˜์•ผ experianUrl๋กœ๋ถ€ํ„ฐ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค..
  // => Concurrentlyํ•˜๊ฒŒ ๋‘๊ฐœ ๋‹ค ์š”์ฒญํ•˜๋Š” ๋ฐฉ๋ฒ•?
  // "Let's work on these two tasks(equifax, experian) concurrently!!"
  // => then, how do we do that?? => async let!
  // MARK: Async-let
  // - async let์„ ์‚ฌ์šฉํ•˜๋ฉด, async ์ž‘์—…์— ๋Œ€ํ•œ reference๋ฅผ ์žก๊ณ  ์žˆ๋Š”๋‹ค. ์ฆ‰์‹œ ๋ฐ˜ํ™˜๋˜๋ฉฐ, concurrent task๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋œ๋‹ค.
  // - async let์„ ๋ถ™์˜€๋‹ค๋ฉด ๋’ค์— ๋ถ™์—ฌ ์‚ฌ์šฉํ–ˆ๋˜ try await์€ ๋ช…์‹œํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.(ex) ์•„๋ž˜ ์ฝ”๋“œ์˜ URLSession ์•ž์— try await๋ฅผ ๋ช…์‹œํ•  ์˜๋ฌด๊ฐ€ ์—†์Œ
  // * ์•„๋ž˜ equifaxData, experianData๋Š” ๋ชจ๋‘ async let์œผ๋กœ ์ •์˜๋œ๋‹ค.
  async let (equifaxData, _) = URLSession.shared.data(from: equifaxUrl)
  async let (experianData, _) = URLSession.shared.data(from: experianUrl)
  
  // custom code
  // async throws ๋ฉ”์„œ๋“œ๋กœ๋ถ€ํ„ฐ async let ์ƒ์ˆ˜๋ฅผ ๋ฐ›์€ ๊ฒƒ์ด๋ฏ€๋กœ, ์ด๋ฅผ ์‚ฌ์šฉํ• ๋•Œ๋Š” try await์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
  // ์•„๋ž˜์™€ ๊ฐ™์ด async let ๊ฐ’์— ๋Œ€ํ•œ await(try await)์„ ํ• ๋•Œ ๋น„๋กœ์†Œ suspend ๋œ๋‹ค! ๋”ฐ๋ผ์„œ async task๋Š” ๋™์‹œ์— ๋™์ž‘์‹œํ‚ค๊ณ , ์ดํ›„์— ์‹ค์ œ ๊ฐ’์„ ๋ฐ›๋Š” ๋ถ€๋ถ„์—์„œ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ => API ์š”์ฒญ์€ concurrentlyํ•˜๊ฒŒ ํ•˜๊ณ , ๋ฐ›์€ ๊ฐ’์„ feedingํ• ๋•Œ๋งŒ ์ˆœ์ฐจ์ ์œผ๋กœ ๋‚˜๋ˆ ์คŒ.
  let equifaxCreditScore = try? JSONDecoder().decode(CreditScore.self, from: try await equifaxData)
  let experianCreditScore = try? JSONDecoder().decode(CreditScore.self, from: try await experianData)

async-let Tasks in loop (์–ธ์ œ Concurrentํ•˜๊ฒŒ, Serialํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฐ€)

let ids = [1, 2, 3, 4, 5]
Task {
  for id in ids {
    // * ์•„๋ž˜์™€ ๊ฐ™์ด loop๋ฌธ์—์„œ async/await์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ์•Œ์•„๋‘์–ด์•ผ ํ•  ์ 
    // 1) loop ๋ฌธ์ด ํ•œ๋ฒˆ ๋Œ ๋•Œ, getAPR ๋‚ด์˜ async let task๋“ค์ด concurrent ํ•˜๊ฒŒ ์ˆ˜ํ–‰๋œ๋‹ค.
    // 2) task๋Š” concurrent ํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ, ๊ฒฐ๊ตญ feeding ๋‹จ๊ณ„์—์„œ suspending์ด ๋œ๋‹ค.
    // 3) ๋‘๊ฐœ์˜ task๊ฐ€ ์ „๋ถ€ ๋๋‚˜๊ณ , feeding๊นŒ์ง€ ๋๋‚˜๋ฉด, ๋น„๋กœ์†Œ loop์˜ ๋‹ค์Œ getAPR๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. (๊ฒฐ๊ตญ ๊ฐ getAPR ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ awaitํ•˜๋Š” ๋ผ์ธ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— suspendํ•˜๊ธด ํ•จ. API ์š”์ฒญ์ด concurrent ํ•  ๋ฟ.)
    // => loop๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ , ๋ชจ๋“  getAPR ๋™์ž‘๋“ค์ด concurrentํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋Š” ์ ์„ ์•Œ์•„์•ผ ํ•œ๋‹ค. (task group์„ ํ™œ์šฉํ•˜๋ฉด ์ด ๋˜ํ•œ concurrent ํ•˜๊ฒŒ ๋™์ž‘์€ ๊ฐ€๋Šฅ ํ•จ.)
    // task group์„ ์‚ดํŽด ๋ณด๊ธฐ ์ „์— ๋จผ์ € ์ค‘์š”ํ•œ ์š”์†Œ ์ค‘ ํ•˜๋‚˜์ธ cancelling a task ๋ฅผ ์•Œ์•„๋ณด์ž.
    let apr = try await getAPR(userId: id)
    print(apr)
  }
}

Cancelling a Task, Task.checkCancellation()

let ids = [1, 2, 3, 4, 5]
var invalidIds: [Int] = []
Task {
  for id in ids {
    do {
      // Task.checkCancellation()์„ ์‚ฌ์šฉํ•˜๋ฉด, ์—๋Ÿฌ๊ฐ€ throwing๋˜์–ด๋„ ์ดํ›„์˜ loop task๋ฅผ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ์ง€์† ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.
      try Task.checkCancellation()
      let apr = try await getAPR(userId: id)
      print(apr)
    } catch {
      print(error)
      invalidIds.append(id)
    }
  }
  
  // error๊ฐ€ ๋ฐœ์ƒํ•œ id๋ฅผ ์ถœ๋ ฅ => invalidIdList : 2 4
  print("invalidIdList : \(invalidIds.map { String($0) }.joined(separator: " "))")
}

Group Tasks

- withTaskGroup, withThrowingTaskGroup (group.addTask { ... })
// MARK: 41. Group Tasks
// async let ์„ loop๋ฌธ์—์„œ ์‚ฌ์šฉํ•˜๋ฉด lopp ๋‚ด ๊ฐ๊ฐ์˜ task ๋‚ด์—์„œ API ์š”์ฒญ์€ concurrent ํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ ๊ฒฐ๊ตญ feeding ๊ณผ์ •์—์„œ suspend ๋˜๊ณ , ์ด๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.
// => ๋ฃจํ”„ ๋‚ด ๊ฐ๊ฐ์˜ task๋ฅผ ๋ชจ๋‘ concurrentํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? => task groups๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

// getAPR์€ ๊ฐ๊ฐ 2๊ฐœ์˜ API ์š”์ฒญ์„ concurrentํ•˜๊ฒŒ ์ง„ํ–‰ํ•จ
// [Main Task] -> first Group (getAPR) -> two tasks concurrently
//             -> second Group (getAPR) -> two tasks concurrently
//             -> ..... (getAPR) -> two tasks concurrently

let ids = [1, 2, 3, 4, 5]
var invalidIds: [Int] = []
func getAPRForAllUsers(ids: [Int]) async throws -> [Int: Double] {
  var userAPR: [Int: Double] = [:]
  
  // 1) loop ๋‚ด ์ž‘์—…๋“ค์„ concurrentํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด for loop ๋ฐ”๊นฅ์— try await withThrowingTaskGroup์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  // - of: group์— ์ถ”๊ฐ€ํ•  task ๊ฒฐ๊ณผ ํƒ€์ž…
  // - body: group task๊ฐ€ ์ˆ˜ํ–‰๋  ํด๋กœ์ ธ๋ฅผ ์ •์˜
  try await withThrowingTaskGroup(of: (Int, Double).self, body: { group in
    for id in ids {
      // 2) group.addTask { ... } ๋‚ด์— concurrentlyํ•˜๊ฒŒ ๋™์ž‘์‹œํ‚ฌ ์ž‘์—…์„ ์ •์˜, ๊ฒฐ๊ณผ๋Š” ์œ„์—์„œ ์ •์˜ํ•œ (Int, Double) ํŠœํ”Œํƒ€์ž…์œผ๋กœ ๋ฐ˜ํ™˜
      group.addTask {
        // ํ•ด๋‹น ๋ธ”๋Ÿญ์—์„œ๋Š” task ๋ธ”๋Ÿญ ๋ฐ–์˜ ๊ฐ’์€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋‹ค getAPR์˜ ๊ฒฐ๊ณผ๋ฅผ ํŠœํ”Œ๋ฐฉ์‹์œผ๋กœ group task๋กœ ์ถ”๊ฐ€ํ•œ๋‹ค.
        // loop๊ฐ€ one by one์œผ๋กœ ๋™์ž‘์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— dataRacing์„ ๋ฐœ์ƒํ•  ๊ฑฑ์ •๋„ ์—†๋‹ค.
        // ์—ฌ๊ธฐ์˜ ์ž‘์—…์€ loop ๋‚ด ๊ฐ๊ฐ์˜ task ์ค‘ ์–ด๋–ค๊ฒŒ ๊ฐ€์žฅ ๋จผ์ € ์™„๋ฃŒ๋  ์ง€ ์•Œ ์ˆ˜ ์—†์–ด์š”. concurrentํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—!
        return (id, try await getAPR(userId: id))
      }
    }
    
    // 3) group์— ์ถ”๊ฐ€๋œ task๋“ค์„ asyncํ•˜๊ฒŒ ์ฐจ๋ก€๋Œ€๋กœ ์ž‘์—…ํ•œ๋‹ค. ์—ฌ๊ธฐ์—์„œ loop ๋‚ด๋ถ€ ๊ฐ task๋“ค์€ ์ˆœ์ฐจ์ ์œผ๋กœ ๋™์ž‘ํ•˜์—ฌ data racing ๊ฑฑ์ • ์—†๋‹ค.
    for try await (id, apr) in group {
      // loop๋ฌธ์—์„œ ๊ฐ task ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ addTask๋ฅผ ์ˆ˜ํ–‰ํ•˜ใ…— for try await loop์—์„œ ๋น„๋กœ์†Œ ๋”•์…”๋„ˆ๋ฆฌ์— ์…‹ํŒ…์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค. (์—ฌ๊ธฐ๋Š” addTask ๋ธ”๋Ÿญ ๋‚ด๋ถ€๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ, ์™ธ๋ถ€ ๊ฐ’ ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅ
      userAPR[id] = apr
    }
  })

  return userAPR
}

Task {
  let userAPRs = try await getAPRForAllUsers(ids: ids)
  print(userAPRs)
}

Additional Task Group Example

getting random images concurrently and asynchronously, and awaiting after that time.
// task group์„ ์‚ฌ์šฉํ•ด์„œ loop ๋‚ด์˜ ๊ฐ image ์š”์ฒญ์„ ๋ชจ๋‘ concurrentํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•ด๋ณด์ž.
  func getRandomImages(ids: [Int]) async throws -> [RandomImage] {
    try await withThrowingTaskGroup(of: (Int, RandomImage).self, body: { group in
      for id in ids {
        // ๋ฃจํ”„๋‚ด ๊ฐ task ๊ฐ๊ฐ concurret task group์„ ๋งŒ๋“ ๋‹ค.
        // task group ๋‚ด์˜ addTask ํด๋กœ์ ธ ๋‚ด๋ถ€์— concurrently ๋™์ž‘์‹œํ‚ฌ ์ž‘์—…์„ ์ž‘์—…ํ•˜๊ณ , of: ๋ ˆ์ด๋ธ”์— ์„ค์ •ํ•œ ํƒ€์ž…์— ๋งž๊ฒŒ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
        group.addTask {
          let randomImage = try await self.getRandomImage(id: id)
          return (id, randomImage)
        }
      }
      
      // group์— ์ถ”๊ฐ€ํ–ˆ๋˜ task๋“ค์„ ์ˆœ์ฐจ์ ์œผ๋กœ suspendingํ•˜์—ฌ ๊ฒฐ๊ณผ๊ฐ’์„ randomImages์— appendingํ•œ๋‹ค.
      // => ๋ชจ๋“  getRandomImage ์š”์ฒญ๋“ค์€ concurrently ๋™์ž‘์„ ํ•œ๋‹ค. ์ดํ›„ ์•„๋ž˜ for try await loop์—์„œ suspending์„ ํ•˜๋ฉฐ, ์ˆ˜์‹ ํ•œ ๊ฐ’์„ ์ˆœ์ฐจ์ ์œผ๋กœ randomImages์— ์ถ”๊ฐ€ํ•œ๋‹ค.
      // => task group์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„๋•Œ : loop์˜ ๊ฐ task ๋‚ด๋ถ€ ๋™์ž‘์€ asyncํ•˜์ง€๋งŒ, ๊ฐ getRandomImage ๊ฒฐ๊ณผ๊ฐ’์„ ์–ป๊ธฐ ์ „๊นŒ์ง€ suspending๋˜์–ด ๋ฃจํ”„ ๋‹ค์Œ task(getRandomImage)๋ฅผ ๋™์‹œ์— ์ˆ˜ํ–‰ํ•˜์ง€ ๋ชปํ–ˆ์Œ.
      // => task group์„ ์‚ฌ์šฉํ–ˆ์„๋•Œ : loop์˜ ๊ฐ task๋Š” concurrently, asynchronous ํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. suspending์€ for try await loop์—์„œ ๋ฐœ์ƒํ•œ๋‹ค.
      for try await (_, randomImage) in group {
        self.randomImages.append(randomImage)
      }
    })
    
    return randomImages
  }

Section 9: Project Time - Random Images and Random Quotes

์•„๋ž˜์™€ ๊ฐ™์€ ์š”์†Œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ randomImage API ์š”์ฒญ์„ concurrentํ•˜๊ฒŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค.

  • structured concurrency : async let
    • ๋‹ค์ˆ˜์˜ API ์š”์ฒญ์„ concurrentํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•˜๊ณ , feeding ๋‹จ๊ณ„(await ์‚ฌ์šฉ ์œ„์น˜)์—์„œ suspendํ•˜์—ฌ ์ˆœ์ฐจ์ ์œผ๋กœ feeding์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • unstructured concurrency : Task { ... }
    • ๊ฐ€์žฅ ์•ž ๋‹จ์—์„œ await ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Task ๋ธ”๋Ÿญ ๋‚ด๋ถ€์—์„œ ์š”์ฒญํ•œ๋‹ค. ํ˜น์€, View์˜ onAppear ์ด๋ฒคํŠธ ์‹œ await ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด, .task { ... } viewModifier๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • task group : withThrowingTaskGroup, withTaskGroup (group.addTask)
    • ๋ชจ๋“  loop์˜ Task(๊ฐ๊ฐ random, quote API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” task)๋“ค์„ concurrentํ•˜๊ฒŒ ๋™์ž‘์‹œํ‚ฌ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    • withThrowingTaskGroup, withTaskGroup ๋‚ด์—์„œ concurrentํ•˜๊ฒŒ ๋™์ž‘ํ•ด์•ผํ•  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ ํ›„(addTask), ๊ทธ ๋‹ค์Œ for await - in loop / for try await - in loop ๋ฌธ ๋‚ด์—์„œ ์š”์ฒญ ๊ฒฐ๊ณผ๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ feeding ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • @MainActor
    • @MainActor๋ฅผ ์ง€์ •ํ•œ ์˜์—ญ์€ ํ•ญ์ƒ ๋ฉ”์ธ์Šค๋ ˆ๋“œ์—์„œ์˜ ๋™์ž‘์ด ๋ณด์žฅ๋˜๋ฏ€๋กœ ๋‚ด๋ถ€์— ์ถ”๊ฐ€์ ์œผ๋กœ DispatchQueue.main.async { ... }, MainActor.run { ... } ์™€ ๊ฐ™์€ thread ๋ช…์‹œ๋ฅผ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • Section 9 ์˜ˆ์ œ ์•ฑ ๋™์ž‘ ๊ฒฐ๊ณผ (concurrentํ•˜๊ฒŒ random image, quote๋ฅผ ์š”์ฒญ ๋ฐ ์ˆ˜์‹  ํ•˜์—ฌ UI ๋žœ๋”๋ง)

Sesion 10: AsyncSequence

  • AsyncSequence Availability

    • iOS 13.0+
  • Sequence protocol์— Async ์„ฑ๊ฒฉ์ด ์ถ”๊ฐ€๋œ ๊ฒƒ

  • Sequence์™€ ๊ฑฐ์˜ ๋™์ผํ•˜๋‹ค. (for loop์— ์‚ฌ์šฉํ•˜๊ณ  makeIterator, next๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค๊ฑฐ๋‚˜, operator ์‚ฌ์šฉ ๋“ฑ... ์œ ์‚ฌ)

    • map, allSatisfy, max, prefix, compactMap, zip, flatMap, dropFirst, contains, filter, reduce ๋“ฑ ๋ชจ๋‘ ์‚ฌ์šฉ ๊ฐ€๋Šฅ!
  • related posting link : https://0urtrees.tistory.com/361

AsyncSequence ๊ฐ€ ์•„๋‹Œ Sequence์™€ async/await์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ• ๋•Œ ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ์ 

// MARK: - Section 10: AsyncSequence
// MARK: Loop Over Sequence Without AsyncSequence
// - AsyncSequence๋ฅผ ํ™œ์šฉํ•ด๋ณด๊ธฐ์— ์•ž์„œ์„œ ๋จผ์ € ์ผ๋ฐ˜ Sequence๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž.

import SwiftUI
import PlaygroundSupport

extension URL {
  func allLines() async -> Lines {
    Lines(url: self)
  }
}

struct Lines: Sequence {
  
  let url: URL
  
  func makeIterator() -> some IteratorProtocol {
    let lines = (try? String(contentsOf: url))?.split(separator: "\n") ?? []
    return LinesIterator(lines: lines)
  }
}

// IteratorProtocol์„ conformํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” next() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.
struct LinesIterator: IteratorProtocol {
  typealias Element = String
  var lines: [String.SubSequence]
  
  // struct ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ์— ๋ฉค๋ฒ„ ๋ณ€๊ฒฝ์ด ์žˆ์œผ๋ฏ€๋กœ mutating์„ ๋ช…์‹œํ•œ๋‹ค.
  mutating func next() -> Element? {
    if lines.isEmpty {
      return nil
    }
    return String(lines.removeFirst())
  }
}

let endPointURL = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.csv")!

Task {
  // Sequene์— ๋Œ€ํ•œ await ๋™์ž‘์„ ํ•˜๊ณ  ์žˆ๋‹ค. -> allLines()์—์„œ ๋ชจ๋“  line๋“ค์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋‚œ ์ดํ›„์—์•ผ loop์˜ ๊ฐ line์ด ์ถœ๋ ฅ๋œ๋‹ค.
  // ์•„๋ž˜ ๋ผ์ธ์€ endPointURL.allLines()๊ฐ€ ๋จผ์ € ์‹คํ–‰๋˜์–ด ๋ชจ๋“  ๋™์ž‘์ด ์™„๋ฃŒ๋˜๋ฉด -> ๊ทธ๋•Œ์„œ์•ผ iterate ํ•˜๊ฒŒ ๋œ๋‹ค.
  for line in await endPointURL.allLines() {
    // endPointURL.allLines() ์ž‘์—…์ด ๋๋‚˜๊ธฐ ์ „๊นŒ์ง„ ์•„๋ž˜ ๋ผ์ธ์€ ์‹œ์ž‘๋„ ๋ชปํ•œ๋‹ค... allLines() ์ž‘์—…์ด ๋งค์šฐ ํฌ๋‹ค๋ฉด ๋งค์šฐ ๋น„ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ ๋  ์ˆ˜ ์žˆ๋‹ค.
    print("line : \(line)")
  }
}

Sequence + async/await ๋Œ€์‹  AsyncSequene + for try await ์„ ์‚ฌ์šฉํ•ด๋ณด์ž

  • ์•ž์„œ Sequence๋ฅผ ์‚ฌ์šฉํ–ˆ์„๋•Œ๋Š” ๋ชจ๋“  lines์˜ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•œ ๋’ค์— ๋’ค๋Šฆ๊ฒŒ iterator๊ฐ€ ๋Œ์•„๊ฐ”๋‹ค. ์ด๋Š” big pause๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
  • AsyncSequence + for try await์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ๊ฐ์˜ line task๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ ์ž‘์—…์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— big pause๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. (๊ฐ๊ฐ์˜ iterator์— ๋Œ€ํ•œ ์ž‘์—…์ด asyncํ•˜๊ฒŒ ๋™์ž‘, ํ•˜๋ฉด ์™„๋ฃŒ๋˜๋Š”๋Œ€๋กœ ๊ทธ ๋‹ค์Œ iterator์— ๋Œ€ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰)
let endPointURL = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.csv")!

// Task { ... } -> unstructured concurrency
Task {
  // endPointURL.lines๋Š” AsyncLineSequence<URL.AsyncBytes> ํƒ€์ž…
  // AsyncSequence๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด for await - in / for try await - in loop๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  // Sequence๋ฅผ ์‚ฌ์šฉํ–ˆ์„๋•Œ์—๋Š” endPointURL.allLines()์— ๋Œ€ํ•œ ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚˜๊ณ  ๋‚˜์„œ์•ผ ์ˆœํšŒ๋ฅผ ํ–ˆ์ง€๋งŒ....
  // -> AsyncSequence์™€ for try await๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด big pauceํ•  ํ•„์š”์—†์ด ์ˆœํšŒ๋ฅผ ํ•˜๋ฉฐ ๊ฐ line์— ๋Œ€ํ•œ try await ์ž‘์—…์„ ์ง„ํ–‰ํ•œ๋‹ค.
  for try await line in endPointURL.lines {
    print(line)
  }
}

/*
Task {
  // Sequene์— ๋Œ€ํ•œ await ๋™์ž‘์„ ํ•˜๊ณ  ์žˆ๋‹ค. -> allLines()์—์„œ ๋ชจ๋“  line๋“ค์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋‚œ ์ดํ›„์—์•ผ loop์˜ ๊ฐ line์ด ์ถœ๋ ฅ๋œ๋‹ค.
  // ์•„๋ž˜ ๋ผ์ธ์€ endPointURL.allLines()๊ฐ€ ๋จผ์ € ์‹คํ–‰๋˜์–ด ๋ชจ๋“  ๋™์ž‘์ด ์™„๋ฃŒ๋˜๋ฉด -> ๊ทธ๋•Œ์„œ์•ผ iterate ํ•˜๊ฒŒ ๋œ๋‹ค.
  for line in await endPointURL.allLines() {
    // endPointURL.allLines() ์ž‘์—…์ด ๋๋‚˜๊ธฐ ์ „๊นŒ์ง„ ์•„๋ž˜ ๋ผ์ธ์€ ์‹œ์ž‘๋„ ๋ชปํ•œ๋‹ค... allLines() ์ž‘์—…์ด ๋งค์šฐ ํฌ๋‹ค๋ฉด ๋งค์šฐ ๋น„ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ ๋  ์ˆ˜ ์žˆ๋‹ค.
    print("line : \(line)")
  }
}
*/

Built-In-Functions using AsyncSequence

  • There're a lot of built-in functions using AsyncSequence
// MARK: 55. Built-In AsyncSequences in iOS Framework
// - AsyncSequences๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค์–‘ํ•œ framework ๋‚ด์žฅ ๊ธฐ๋Šฅ(Built-In-Functions)๋“ค์ด ์žˆ๋‹ค.
// ex) ๋กœ์ปฌํŒŒ์ผ, URL์˜ byte ์ฝ์„๋•Œ, NotificationCenter๋กœ๋ถ€ํ„ฐ ํŠน์ • ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌํ• ๋•Œ ๋“ฑ...

import Foundation
import UIKit
import _Concurrency

// txtํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์™€์„œ line์„ ์ถœ๋ ฅ
let paths = Bundle.main.paths(forResourcesOfType: "txt", inDirectory: nil)
let fileHandle = FileHandle(forReadingAtPath: paths[0])
/*
Task {
  for try await line in fileHandle!.bytes {
    // print async bytes
    print(line)
  }
}
*/

// ์ด์ฒ˜๋Ÿผ URL, local data์— ๋Œ€ํ•œ byte๋ฅผ ์ฝ์„๋•Œ๋„ AsyncSequence๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
let url = URL(string: "https://www.google.com")!
Task {
  let (bytes, _) = try await URLSession.shared.bytes(from: url)
  for try await byte in bytes {
    print(byte)
  }
}

Task {
  let center = NotificationCenter.default
  await center.notifications(named: UIApplication.didEnterBackgroundNotification).first {
    guard let key = $0.userInfo?["key"] as? String else { return false }
    return key == "SomeValue"
  }
}

AsyncSequence๋ฅผ ์•Œ์•„์„œ ๋งŒ๋“ค์–ด์ฃผ๋Š” AsyncStream, AsyncThrowingStream (Swift 5.7+)

  • Concurrency์—์„œ๋Š” AsyncStream, AsyncSequence๋ฅผ ์ค€์ˆ˜ํ•˜์—ฌ ๋น„๋™๊ธฐ Iterator๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ณ ๋„ AsyncSequence๋ฅผ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • AsyncStream์˜ Continuation์—์„œ yield๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ stream์— ์ œ๊ณตํ•˜๊ฑฐ๋‚˜, ๋”์ด์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ, finish๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ํ˜น์€ ๋ฐ์ดํ„ฐ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ yield.(with: .success()), yield.(with: .failure))๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. failure๋กœ ์ „๋‹ฌํ• ๋•Œ๋Š” AsyncThrowingStream์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

  • withCheckedThrowingContinuation, withCheckedContinuation์€ ๋‹จ์ˆœ, async await method์œผ๋กœ ๋ฐ”๊พธ๊ธฐ ์–ด๋ ค์šด ์• ๋“ค์„ ๋ž˜ํ•‘ํ•ด์„œ async await ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋Šฅ์ด์—ˆ๋‹ค๋ฉด, AsyncStream์€ ์—ฐ์†์ ์ธ ๋น„๋™๊ธฐ ๋™์ž‘์œผ๋กœ for await, for try await ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์‰ฝ๊ฒŒ AsyncStream์œผ๋กœ ๋ณ€ํ™˜ ์‹œ์ผœ์ฃผ๋Š” ๊ธฐ๋Šฅ

  • AsyncStream, for await (with AsyncStream)์‚ฌ์šฉ ์˜ˆ์‹œ

import SwiftUI

func countdown() async {
  let counter = AsyncStream<String> { continuation in
    var countdown = 3
    Timer.scheduledTimer(
      withTimeInterval: 1.0,
      repeats: true
    ) { timer in
      guard countdown > 0 else {
        timer.invalidate()
        // .failure ์ด๋ฒคํŠธ๋„ ์ „๋‹ฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, AsyncStream ๋Œ€์‹ , AsyncThrowingStream์„ ์‚ฌ์šฉํ•ด์•ผํ•จ
        continuation.yield(with: .success("\(Date()) bye bye!"))
        // countdown์ด ๋ชจ๋‘ ๋๋‚˜๋ฉด, continuation ์ข…๋ฃŒ
        return
      }
      
      // countdown ํ• ๋•Œ๋งˆ๋‹ค yield๋กœ AsyncStream์— ์ œ๊ณตํ•  ๊ฐ’์„ ์ „๋‹ฌ
      continuation.yield("\(Date()) countdown : \(countdown)")
      // Timer์— ์˜ํ•ด 1์ดˆ๋งˆ๋‹ค countdown์ด 1์”ฉ ๊ฐ์†Œ, 0์ด๋˜๋ฉด Timer ์ค‘์ง€
      countdown -= 1
    }
  }
  // AsyncStream์ธ counter๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ await ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋‹ค.
  // ๋งŒ์•ฝ AsyncThrowingStream์ด์—ˆ๋‹ค๋ฉด, for try await ๋กœ ์ž‘์—…์ด ๋˜์—ˆ์„ ๊ฒƒ์ด๋‹ค.
  for await count in counter {
    print(count)
  }
}

func runAsyncStreamTask() {
  Task {
    await countdown()
  }
}
  • AsyncThrowingStream for try await (with AsyncThrowingStream<String, Error>) ์‚ฌ์šฉ ์˜ˆ์‹œ
import SwiftUI

enum MyError: Error {
  case invalidCount
}

func countdown() async throws {
  let counter = AsyncThrowingStream<String, Error> { continuation in
    var countdown = 3
    Timer.scheduledTimer(
      withTimeInterval: 1.0,
      repeats: true
    ) { timer in
      guard countdown > 0 else {
        timer.invalidate()
        // .failure ์ด๋ฒคํŠธ๋„ ์ „๋‹ฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, AsyncStream ๋Œ€์‹ , AsyncThrowingStream์„ ์‚ฌ์šฉํ•ด์•ผํ•จ
        continuation.yield(with: .success("\(Date()) bye bye!"))
        // countdown์ด ๋ชจ๋‘ ๋๋‚˜๋ฉด, continuation ์ข…๋ฃŒ
        return
      }
      
      // ํŠน์ • ์ƒํ™ฉ์— ์—๋Ÿฌ๋ฅผ ๋˜์ง€๊ณ  ์‹ถ์„๋•Œ .failure ์ด๋ฒคํŠธ๋ฅผ ๋ณด๋‚ด๋ฉด AsyncSequence์— ์—๋Ÿฌ์ด๋ฒคํŠธ๊ฐ€ ์ œ๊ณต๋œ๋‹ค.
      if countdown == 1 {
        continuation.yield(with: .failure(MyError.invalidCount))
      }
      // countdown ํ• ๋•Œ๋งˆ๋‹ค yield๋กœ AsyncStream์— ์ œ๊ณตํ•  ๊ฐ’์„ ์ „๋‹ฌ
      continuation.yield("\(Date()) countdown : \(countdown)")
      // Timer์— ์˜ํ•ด 1์ดˆ๋งˆ๋‹ค countdown์ด 1์”ฉ ๊ฐ์†Œ, 0์ด๋˜๋ฉด Timer ์ค‘์ง€
      countdown -= 1
    }
  }
  // AsyncThrowingStream์ธ counter๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ try await ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋‹ค.
  for try await count in counter {
    print(count)
  }
}

func runAsyncThrowingStreamTask() {
  // ๋งˆ์ง€๋ง‰ Task { ... } ๋ธ”๋Ÿญ ์‚ฌ์šฉ ๋ถ€์—์„œ๋Š” ๋ฉ”์„œ๋“œ ๋ฐ˜ํ™˜๋ถ€ ์•ž์— async, async throws๋ฅผ ๋ถ™ํžˆ์ง€ ์•Š๋Š”๋‹ค.
  Task {
    do {
      try await countdown()
    } catch {
      // error๋ฅผ throwํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ๋ฉ”์„œ๋“œ์— throws ํ‚ค์›Œ๋“œ ์„ค์ • ์•ˆํ•ด๋„ ๋จ
      print(error.localizedDescription)
    }
  }
}

runAsyncThrowingStreamTask()
  • BitcoinPriceMonitor callback๋“ค์„ AsyncStream์œผ๋กœ AsyncSeqenceํ™”ํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ
    • TaskGroup, AsyncStream ๋ชจ๋‘ ๋‚ด๋ถ€์ ์œผ๋กœ AsyncSequence์ž„. ๊ทธ๋ž˜์„œ ๋ชจ๋‘ for try await, for await loop์—
    • callback stream -> AsyncSequence๋กœ ๋ฐ”๊พธ์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
      • ๊ธฐ์กด Sequence๋ฅผ ์ฑ„ํƒํ•œ ์• ๋“ค์ด ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ๋‹ค์–‘ํ•œ ์—ฐ์‚ฐ์ž๋ฅผ ํ•จ๊ป˜ ํ™œ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
      • Async/Awaitํ•˜๊ฒŒ ๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
// MARK: 56. Adapting Existing Callbacks or Handlers to AsyncSequence Using AsyncStream

import UIKit

class BitcoinPriceMonitor {
  
  var price: Double = 0.0
  var timer: Timer?
  var priceHandler: (Double) -> Void = { _ in }
  
  // Timer ์„ค์ •์— #selector๋กœ ์ง€์ •๋˜๊ธฐ ์œ„ํ•ด์„œ๋Š” @objc๋ฅผ ๋ถ™์—ฌ์„œ Objective-C runtime์—์„œ ์†Œํ†ต๊ฐ€๋Šฅํ•˜๋„๋ก ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  @objc func getPrice() {
    priceHandler(Double.random(in: 20000...40000))
  }
  
  func startUpdating() {
    timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(getPrice), userInfo: nil, repeats: true)
  }
  
  func stopUpdating() {
    timer?.invalidate()
  }
}

/*
let bitcoinPriceMonitor = BitcoinPriceMonitor()
// ์•„๋ž˜์™€ ๊ฐ™์€ ํƒ€์ด๋จธ ๊ฐ’์„ ์ˆ˜์‹ ํ•˜๋Š” ํด๋กœ์ ธ๋ฅผ AsyncStreamํ™”ํ•ด์„œ Async/Await ํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ? -> ๊ฐ€๋Šฅํ•จ.
bitcoinPriceMonitor.priceHandler = {
  print($0)
}

bitcoinPriceMonitor.startUpdating()
*/

let bitcoinPriceStream = AsyncStream(Double.self) { continuation in
  let bitcoinPriceMonitor = BitcoinPriceMonitor()
  bitcoinPriceMonitor.priceHandler = {
    // AsyncSequence์— ์ œ๊ณต๋  ๊ฐ’์„ ์ „๋‹ฌํ• ๋•Œ yield๋ฅผ ์‚ฌ์šฉ
    continuation.yield($0)
  }
  // continuation์„ ํ†ตํ•ด onTermination callback ํด๋กœ์ ธ๋ฅผ ์„ค์ • ๊ฐ€๋Šฅ
  // continuation.onTermination = { _ in }
  bitcoinPriceMonitor.startUpdating()
}

Task {
  // AsyncStream์„ ์‚ฌ์šฉํ• ๋•Œ ์žฅ์ 
  // 1) ์ฝœ๋ฐฑ ์ŠคํŠธ๋ฆผ๋“ค์„ AsyncSequene๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ์ด๋•Œ ๊ธฐ์กด Sequence๋ฅผ ์ฑ„ํƒํ•œ ์• ๋“ค์ด ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ๋‹ค์–‘ํ•œ ์—ฐ์‚ฐ์ž๋ฅผ ํ•จ๊ป˜ ํ™œ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
  // 2) Async/Awaitํ•˜๊ฒŒ ๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  for await bitcoinPrice in bitcoinPriceStream {
    print(bitcoinPrice)
  }
}
  • AsyncThrowingStream ๋‚ด์— Task.sleep()์„ ํ™œ์šฉํ•œ ํƒ€์ด๋จธ ๊ธฐ๋Šฅ ๊ตฌํ˜„๋ฐฉ๋ฒ•
    • ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ, continuation์„ ๊ตณ์ด ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฐฉ์‹์œผ๋กœ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ, ๊ณ„์† ํŠน์ • ์ž‘์—…์„ ํ•˜๋‹ค๊ฐ€ nil์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด stream์ด ์ข…๋ฃŒ๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Œ.
func countdown() async throws {
    var countdown = 3
    let counter = AsyncThrowingStream<String, Error> {
        do {
          	// ์•„๋ž˜ ์ฒ˜๋Ÿผ 1๋กœ ์ง€์—ฐ ์ž‘์—…์„ ์ฃผ๋ฉด์„œ Timerํด๋ž˜์Šค ์—†์ด Timer ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ ๊ฐ€๋Šฅ
            try await Task.sleep(nanoseconds: 1_000_000_000)
        } catch {
            return nil
        }
        // ํ•œ๋ฒˆ์˜ stream์ž‘์—… ๋ธ”๋Ÿญ์ด ๋๋‚ ๋•Œ๋งˆ๋‹ค countdown์„ 1์”ฉ ์ค„์ž„
        defer { countdown -= 1 }
        
        if countdown == 1 {
          	// AsyncThrowingStream์ด๋ฏ€๋กœ, throw๋กœ Error ๋˜์งˆ ์ˆ˜๋„ ์žˆ์Œ 
            throw NSError(domain: "error", code: 1)
        }
        
        switch countdown {
        case (1...): return "\(Date()) \(countdown)..."
        case 0: return "\(Date()) ๐ŸŽ‰ Hello"
        default: return nil
        }
    }
    
    for try await count in counter {
        print(count)
    }
}

func run() {
    Task {
        do {
            try await countdown()
        } catch {
            print(error)
        }
    }
}

/** Output
2022-06-22 15:58:23 +0000 3...
2022-06-22 15:58:24 +0000 2...
Error Domain=error Code=1 "(null)"
*/

Section 11. Concurrent Programming: Problem and Solutions

์€ํ–‰ ๊ณ„์ขŒ ์ถœ๊ธˆ์ด ๋™์‹œ์ ์œผ๋กœ ์š”์ฒญ๋  ๋•Œ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ 

  • ๋™์‹œ์ ์œผ๋กœ(using concurrent queue) ์ถœ๊ธˆ์š”์ฒญ์ž‘์—…์ด ์ง„ํ–‰๋˜๋ฉด ์ž”๊ณ ๊ฐ€ -(minus)๊ฐ€ ๋  ์ˆ˜๋„ ์žˆ์Œ
    • serial queue, actor ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ
// MARK: - Section 11. Concurrent Programming: Problem and Solutions
// MARK: 58. Problem: Bank Account Withdraw (์€ํ–‰ ๊ณ„์ขŒ ์ถœ๊ธˆ ๋ฌธ์ œ)
// MARK: 59. Solutiion 1: Bank Account Withdraw Using Serial Queue
  
import UIKit

class BankAccount {
  var balance: Double
  
  init(balance: Double) {
    self.balance = balance
  }
  
  func withdraw(_ amount: Double) {
    if balance >= amount {
      let processingTime = UInt32.random(in: 0...3) // ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์€ 1 ~ 3์ดˆ๋กœ ๋™์ž‘
      print("[withdraw] Processing for \(amount) \(processingTime) seconds")
      sleep(processingTime) // 3์ดˆ ํ›„ amount๋งŒํผ ์ถœ๊ธˆ์„ ์‹œ๋„
      print("withdrawing \(amount) from account")
      balance -= amount
      print("Balance is \(balance)")
    }
  }
}

// Q. ๋งŒ์•ฝ ๋™์‹œ์— ๋งŽ์€ ์š”์ฒญ์ด ์˜ค๊ฒŒ ๋œ๋‹ค๋ฉด??

let bankAccount = BankAccount(balance: 500)
let queue = DispatchQueue(label: "ConcurrentQueue", attributes: .concurrent)

queue.async {
  bankAccount.withdraw(300)
}

queue.async {
  bankAccount.withdraw(500)
}

// concurrent ํ•˜๊ฒŒ ๋‹ค์ˆ˜์˜ ์ถœ๊ธˆ ์š”์ฒญ์„ ์‹คํ–‰ ์‹œ ์ž”๊ณ ๊ฐ€ -๊ฐ€ ๋˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒ!!
/*
 [withdraw] Processing for 300.0 2 seconds
 [withdraw] Processing for 500.0 3 seconds
 withdrawing 300.0 from account
 Balance is 200.0
 withdrawing 500.0 from account
 Balance is -300.0
*/

์€ํ–‰ ์ถœ๊ธˆ๋ฌธ์ œ Solutions

  • Actor (section 12์—์„œ ์•Œ์•„๋ณด์ž)
  • NSLock ์ธ์Šคํ„ด์Šค์˜ lock(), unlock()์„ ํ†ตํ•œ locking
  • concurrent queue ๋Œ€์‹  serial queue๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ
// MARK: - Section 11. Concurrent Programming: Problem and Solutions
// MARK: 58. Problem: Bank Account Withdraw (์€ํ–‰ ๊ณ„์ขŒ ์ถœ๊ธˆ ๋ฌธ์ œ)
// MARK: 59. Solution 1: Bank Account Withdraw Using Serial Queue
// MARK: 60. Solution 2: Bank Account Withdraw Using Locks(NSLock)
  
import UIKit

class BankAccount {
  var balance: Double
  let lock = NSLock()
  
  init(balance: Double) {
    self.balance = balance
  }
  
  func withdraw(_ amount: Double) {
    // NSLock์„ ํ†ตํ•œ locking์„ ํ•˜๋ฉด, ๋™์‹œ์  ์ž‘์—…์œผ๋กœ ์ธํ•ด ์ž”๊ณ ๊ฐ€ minus๊ฐ€ ๋˜๋Š”๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์ค€๋‹ค. (ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ์— ํ•˜๋‚˜์˜ ๋™์ž‘์”ฉ๋งŒ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅ)
    // lock() ์‚ฌ์šฉ ์‹œ, ๋ฐ˜๋“œ์‹œ unlock()์„ ์ง์ ‘ ์ง€์ •ํ•ด์ฃผ์–ด์•ผ ํ•˜๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. ๋งŒ์•ฝ ๊นŒ๋จน๊ณ  unlock()์„ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด, ํ•ด๋‹น ์Šค๋ ˆ๋“œ์˜ ๋‹ค์Œ ์ž‘์—…์ด ๋งค์šฐ ์ง€์—ฐ๋  ์ˆ˜ ์žˆ๋‹ค.
    // -> ์ฐจ์„ ์ฑ…์œผ๋กœ actor๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    lock.lock()
    if balance >= amount {
      let processingTime = UInt32.random(in: 0...3) // ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์€ 1 ~ 3์ดˆ๋กœ ๋™์ž‘
      print("[withdraw] Processing for \(amount) \(processingTime) seconds")
      sleep(processingTime) // 3์ดˆ ํ›„ amount๋งŒํผ ์ถœ๊ธˆ์„ ์‹œ๋„
      print("withdrawing \(amount) from account")
      balance -= amount
      print("Balance is \(balance)")
    }
    lock.unlock()
  }
}

// Q. ๋งŒ์•ฝ ๋™์‹œ์— ๋งŽ์€ ์š”์ฒญ์ด ์˜ค๊ฒŒ ๋œ๋‹ค๋ฉด??

let bankAccount = BankAccount(balance: 500)
// ์ž‘์—…์„ concurrentํ•˜๊ฒŒ ํ•˜์ง€์•Š๊ณ , serialํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋ฉด, ๋™์‹œ์— ์ž‘์—…์ด ์ˆ˜ํ–‰๋˜๋Š” ์ผ์ด ์—†๊ธฐ์— ์ž”๊ณ ๊ฐ€ -๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค.
let queue = DispatchQueue(label: "Serial Queue")

queue.async {
  bankAccount.withdraw(300)
}

queue.async {
  bankAccount.withdraw(500)
}

// concurrent ํ•˜๊ฒŒ ๋‹ค์ˆ˜์˜ ์ถœ๊ธˆ ์š”์ฒญ์„ ์‹คํ–‰ ์‹œ ์ž”๊ณ ๊ฐ€ -๊ฐ€ ๋˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒ!!
/*
 [withdraw] Processing for 300.0 2 seconds
 [withdraw] Processing for 500.0 3 seconds
 withdrawing 300.0 from account
 Balance is 200.0
 withdrawing 500.0 from account
 Balance is -300.0
*/

// serial ํ•˜๊ฒŒ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜, NSLock()์˜ lock(), unlock()์„ ์‚ฌ์šฉ ์‹œ, ์ž”๊ณ ๊ฐ€ -๊ฐ€ ๋˜๋Š” ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐ์ด ๋จ.
// -> ๋˜๋‹ค๋ฅธ ํ•ด๊ฒฐ๋ฐฉ๋ฒ• => Actor๋ฅผ Section 12์—์„œ ์•Œ์•„๋ณด์ž!!
/*
 [withdraw] Processing for 300.0 2 seconds
 withdrawing 300.0 from account
 Balance is 200.0
 // ๋‘๋ฒˆ์งธ ์ž‘์—…์€ ์‹คํ–‰๋˜์ง€ ์•Š์Œ. ์ถœ๊ธˆ์ด ๋ถˆ๊ฐ€๋Šฅ(์ถœ๊ธˆํ•  amount๊ฐ€ ์ž”๊ณ ๋ณด๋‹ค ํผ)ํ•˜๊ธฐ์—
*/

Section 12: What are Actors?

protect mutable state, accessing Actor isolated states, MainActor, Nonisolated instances

  • actor๋Š” class์™€ ์œ ์‚ฌํ•˜๋‚˜, ์ƒ์†์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ์—์„œ๋งŒ ๋™์ž‘ํ•˜์—ฌ data racing ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋‚ด๋ถ€์— ์ •์˜๋œ ๋ฉ”์„œ๋“œ๋Š” await ํ‚ค์›Œ๋“œ๋กœ ํ˜ธ์ถœ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋‹จ๊ธฐ๊ฐ„์— ๋‹ค์ฐจ๋ก€ ๋ฐ˜๋ณต ํ˜ธ์ถœ์„ ํ•ด๋„, ํ•œ๋ฒˆ์˜ ๋™์ž‘์ด ๋๋‚˜์•ผ ๊ทธ ๋‹ค์Œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
// MARK: - Section 12: What are Actors?
// MARK: 63. Understanding Actors

import SwiftUI

// 1) class๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด
/*
class Counter {
  var value: Int = 0
  
  func increment() -> Int {
    value += 1
    return value
  }
}
// => concurrently ๋™์ž‘ ์‹œ, ์ถœ๋ ฅ ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜์ง€ ์•Š์Œ
*/

/*
// 2) struct๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด
struct Counter {
  var value: Int = 0
  
  mutating func increment() -> Int {
    value += 1
    return value
  }
}
// => concurrently ๋™์ž‘ ์‹œ, ์ถœ๋ ฅ ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜์ง€ ์•Š์Œ
// ๊ฐ’ ๋ณต์‚ฌํ•ด์„œ ํ˜ธ์ถœํ•  ๊ฒฝ์šฐ, 1์ด ๋ฌด์ˆ˜ํžˆ ์ถœ๋ ฅ ๋จ..
*/

// 3) class, struct ๋Œ€์‹  actor๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ
// actor๋Š” ๋‹จ ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ๋ณด์žฅํ•ด์ค€๋‹ค. ๋”ฐ๋ผ์„œ data racing ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋œ๋‹ค.
actor Counter {
  var value: Int = 0
  // ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ์—์„œ ํ•œ๋ฒˆ์— ํ•˜๋‚˜์˜ ๋™์ž‘๋งŒ, ๋™์ž‘์ด ์™„๋ฃŒ๋˜๋ฉด suspended ๋‹ค์Œ ๋™์ž‘์ด ์ˆ˜ํ–‰๋˜๋ฏ€๋กœ ์ถœ๋ ฅ ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ
  // actor ๋‚ด์˜ methods๋Š” await๋ฅผ ๋ถ™ํ˜€์„œ ํ˜ธ์ถœ, ๋‘๊ฐœ ์ด์ƒ์˜ ์Šค๋ ˆ๋“œ์—์„œ ํ•œ๋ฒˆ์— ๋™์ž‘ํ•˜์ง€ ์•Š์Œ
  func increment() -> Int {
    value += 1
    return value
  }
}

struct ContentView: View {
    var body: some View {
      Button {
        let counter = Counter()
        // 1) ๋งŒ์•ฝ concurrent ํ•˜๊ฒŒ ๋™์‹œ์— increment๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด?
        // 100๊นŒ์ง€ ์ฆ๊ฐ€ํ•˜๋ฉด์„œ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ๊ธฐ๋Œ€ํ•˜๊ณ  ์•„๋ž˜์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค๋ฉด? => ์นด์šดํŒ… ๋’ค์ฃฝ๋ฐ•์ฃฝ ์ˆœ์„œ๋กœ ์ถœ๋ ฅ์ด ๋จ... => concurrently ํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋ฏ€๋กœ, ๊ฐœ๋ณ„ ์ž‘์—…๋“ค์— ๋Œ€ํ•œ ์‹œ์ž‘์€ ์ˆœ์„œ๋Œ€๋กœ ๋”๋ผ๋„, ์™„๋ฃŒ๋˜๋Š” ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค.
        DispatchQueue.concurrentPerform(iterations: 100) { _ in
          // 2) ์•„๋ž˜์ฒ˜๋Ÿผ struct์ƒํƒœ counter์˜ copy๋ฅผ ์ƒ์„ฑํ•˜๊ณ , increment()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด? -> ์ „๋ถ€ ๊ฐ’๋ณต์‚ฌ๋กœ zero์—์„œ ์‹œ์ž‘ํ•˜๋ฏ€๋กœ 1์ด ๋ฌด์ˆ˜ํ•˜๊ฒŒ ์ถœ๋ ฅ๋จ.
          // var counter = counter // struct๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
          // print(counter.increment())
          // 3) actor๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž.
          Task {
            // await, try await ๋“ฑ์€ Task ๋ธ”๋Ÿญ ๋‚ด๋ถ€, .task viewModifier ๋‚ด๋ถ€ ๋“ฑ(unstructured concurrency)์—์„œ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.
            // => increment() ์ถœ๋ ฅ ๊ฒฐ๊ณผ, ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋œ๋‹ค!
            print(await counter.increment())
          }
        }
      } label: {
        Text("Increment")
      }
    }
}

Actor example

// MARK: 65. Actors Example: Bank Account Transfer Funds
// MARK: 66. Understanding nonisolated Keyword in Swift
// actor ๋‚ด์—์„œ nonisolated keyword๊ฐ€ ๋ถ™์€ ๋ฉ”์„œ๋“œ๋Š”
// - ๋‚ด๋ถ€์— ๋ณ€๊ฒฝ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์—†๋‹ค. (๋ณ€๊ฒฝํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.)
// - ์™ธ๋ถ€์—์„œ ์‚ฌ์šฉํ• ๋•Œ Task ๋ธ”๋Ÿญ ๋‚ด์— async/await ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. data racing ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์—ฌ์ง€๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

import SwiftUI

enum BankError: Error {
  case insufficientFunds(Double)
}

// ์ด๋ฒˆ์—๋„ BankAccount๋ฅผ actor๋กœ ์„ ์–ธํ–ˆ๋‹ค. ํ•œ๋ฒˆ์— ํ•œ๋ฒˆ์”ฉ๋งŒ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
// concurrent task๋กœ ๊ณตํ†ต์˜ ์ž์›์„ ๋ณ‘ํ–‰์ ์œผ๋กœ ์ฝ๊ฑฐ๋‚˜ ์“ฐ๋Š” ๋ฌธ์ œ์ธ data racing(race condition)์„ ๋ฐฉ์ง€ํ•ด์ฃผ๋ฉฐ ๋‚ด๋ถ€์˜ ๋ฉ”์„œ๋“œ๋Š” async/await ํ•˜๊ฒŒ ๋™์ž‘ํ•ด์•ผ ํ•œ๋‹ค.
actor BankAccount {
  let accountNumber: Int
  var balance: Double
  
  init(accountNumber: Int, balance: Double) {
    self.accountNumber = accountNumber
    self.balance = balance
  }
  
  // getCurrentAPR์€ ๊ณ ์ •๋œ ๊ฐ’๋งŒ ๋ฐ˜ํ™˜ํ•˜์ง€ ๋‚ด๋ถ€์—์„œ ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚˜๋Š” ๋ฉ”์„œ๋“œ๋Š” ์•„๋‹ˆ๋‹ค.
  // ๋”ฐ๋ผ์„œ Data racing์ด ๋ฐœ์ƒํ•  ์ผ์ด ์—†๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ์•ž์— nonisolated๋ฅผ ๋ถ™ํ˜€์„œ actor๊ฐ€ ์•„๋‹Œ struct, class ๋ฉ”์„œ๋“œ์ฒ˜๋Ÿผ ํ˜ธ์ถœํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  // => nonisolated func : "์•ผ ์ด๊ฑฐ race condition ๋ฐœ์ƒํ•  ์ผ ์—†๋Š” ๋†ˆ์ด์•ผ async/await call ๋ฐฉ์‹์„ ์ทจํ•  ํ•„์š”๊ฐ€ ์—†์–ด!"
  nonisolated func getCurrentAPR() -> Double {
    // nonisolated func์€ ๋‚ด๋ถ€์— ๋ณ€๊ฒฝ ์ฝ”๋“œ๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
    // * ๊ฒฝ๊ณ  ๋‚ด์šฉ : Actor-isolated property 'balance' can not be mutated from a non-isolated context
    // balance += 10
    return 0.2
  }
  
  // ๋ฐ˜ํ™˜๋ถ€ ์•ž์— async๋ฅผ ๋ถ™ํ˜€๋„ ์•ˆ๋ถ™ํ˜€๋„ ์™ธ๋ถ€์—์„œ๋Š” await์„ ๋ถ™ํ˜€์„œ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค. actor ๋ฉ”์„œ๋“œ๋‹ˆ๊นŒ.
  func deposit(_ amount: Double) {
    balance += amount
  }
  
  func transfer(amount: Double, to other: BankAccount) async throws {
    if amount > balance {
      throw BankError.insufficientFunds(amount)
    }
    
    balance -= amount
    // other๋Š” actor(BankAccount)์ด๋‹ค. ๋”ฐ๋ผ์„œ deposit ๋ฉ”์„œ๋“œ ๋™์ž‘์„ ์œ„ํ•ด await๋ฅผ ๋ถ™์ธ๋‹ค.
    await other.deposit(amount)
    // other์˜ ๋ชจ๋“  ๋ฉค๋ฒ„๊ฐ€ await์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š”๊ฑด ์•„๋‹ˆ๋‹ค. accountNumber๋Š” ์ƒ์ˆ˜์ด๋ฏ€๋กœ await ์—†์ด๋„ ๋™์ž‘์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
    print(other.accountNumber)
    print("Current Account: \(balance), Other Account: \(await other.balance)")
  }
}

struct ContentView: View {
  var body: some View {
    Button {
      
      let bankAccount = BankAccount(accountNumber: 123, balance: 500)
      let otherAccount = BankAccount(accountNumber: 456, balance: 100)
      
      // getCurrentAPR()์€ actor method์ž„์—๋„ nonisolated func์ด๋ฏ€๋กœ, async/awaitํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
      let _ = bankAccount.getCurrentAPR() // await, try await ๋“ฑ์˜ ์˜ˆ์•ฝ์–ด๊ฐ€ ๋ถ™์ง€ ์•Š๋Š” ๋ชจ์Šต. nonisolated property์ด๊ธฐ ๋•œ.
      // ์€ํ–‰ ์ž”๊ณ  ์ถœ๊ธˆ์„ concurrentํ•˜๊ฒŒ ์ง„ํ–‰ํ•˜๋Š”๋ฐ, ์™„๋ฃŒ ์‹œ์ ์ด ๋’ค์ฃฝ๋ฐ•์ฃฝ์ด ๋œ๋‹ค๋ฉด? ์˜๋„์น˜ ์•Š์€ ์‚ฌ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค!
      DispatchQueue.concurrentPerform(iterations: 100) { _ in
        Task {
          try? await bankAccount.transfer(amount: 300, to: otherAccount)
        }
      }
    } label: {
      Text("Transfer")
    }
  }
}

Actor with protocol practice

import SwiftUI

protocol Human {
  associatedtype Food
  var food: Food { get set }
  
  func eat(food: Food)
}

class Baby: Human {
  // ์•„๋ž˜์ฒ˜๋Ÿผ Food associatedtype์˜ ํƒ€์ž…์„ ์ •์˜ํ• ์ˆ˜๋„ ์žˆ์ง€๋งŒ ๊ด€๋ จ ๋ฉค๋ฒ„๋“ค์˜ ํƒ€์ž…์„ ํ†ตํ•ด ํƒ€์ž…์„ ์ถ”๋ก ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ๋„ ๋จ
  // Human protocol์˜ ํ•„์ˆ˜ ๊ตฌํ˜„ ๋ฉ”์„œ๋“œ, eat์—์„œ food ํƒ€์ž…์ธ Food๋ฅผ Stringํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉํ–ˆ๊ธฐ์— ์ด๋ฅผ ํƒ€์ž… ์ถ”๋ก ํ•˜์—ฌ Food ํƒ€์ž…์„ String์œผ๋กœ ์ธ์‹ํ•จ
  // typealias Food = String
  var food: String
  
  init(food: String) {
    self.food = food
  }

  func eat(food: String) {
    print(food)
  }
}

protocol ActorMan {
  associatedtype Food
  var food: Food { get }
}

actor ActMan: ActorMan {
  typealias Food = String
  // food๋Š” data racing, race condition์ด ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์ „ํ˜€ ์—†๋Š” ๋†ˆ์ด๋ผ nonisolated ๋กœ ์„ ์–ธํ•ด์„œ ์™ธ๋ถ€์—์„œ๋Š” async/await ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ ์•ˆํ•ด๋„ ๋œ๋‹ค.
  // constant๋Š” ์ƒ์ˆ˜์ด๋ฏ€๋กœ, actor ๋ฉค๋ฒ„์ด์ง€๋งŒ nonisolated ๋ช…์‹œ ์•ˆํ•ด๋„ ์™ธ๋ถ€์—์„œ await์œผ๋กœ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Œ
  let constant = "hahhaa"
  // ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ์„ฑ์ด ์—†๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ getter ํ”„๋กœํผํ‹ฐ๋Š” nonisolated ๋ช…์‹œ๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€์—์„œ await ํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ํ•„์š” ์—†๋„๋ก ํ•  ์ˆ˜ ์žˆ์Œ
  nonisolated var food: String {
    return "apple"
  }
  
  init() {}
}

let baby = Baby(food: "apple")
baby.eat(food: "human")

let actorMan = ActMan()
print(actorMan.constant)
print(actorMan.food)

About

Udemy Study for Swift Concurrency

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors