<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>HOB&amp;rsquo;s 개발</title>
    <link>https://red-cherry-ring.tistory.com/</link>
    <description>개발관련 포스팅을 올리는 개인 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 12:39:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>빨간체리반지</managingEditor>
    <image>
      <title>HOB&amp;rsquo;s 개발</title>
      <url>https://tistory1.daumcdn.net/tistory/3046220/attach/6d6d3416317145dabda29d8b8f8dc58b</url>
      <link>https://red-cherry-ring.tistory.com</link>
    </image>
    <item>
      <title>Swift Concurrency #마무리 - 여러 번 보면서 이해한 Swift Concurrency</title>
      <link>https://red-cherry-ring.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 Swift Concurrency와 관련된 WWDC 영상을 보며 전반적인 내용을 훑어보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 역시 해당 영상을 여러 번 반복해서 시청했다. 처음에는 잘 이해되지 않던 개념들도, 다시 보면서 조금씩 정리가 되었고, 영상을 본 뒤 한동안 Concurrency를 사용하지 않다 보면 다시 헷갈리는 부분들도 있었다.&lt;br /&gt;그럴 때마다 내가 작성했던 필기 내용을 다시 읽으면서 많은 도움을 받을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 &lt;b&gt;모르는 내용은 끈기 있게 찾아보고, 스스로 정리하면서 공부하는 방법밖에는 없는 것 같다&lt;/b&gt;는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어 블로그도 작성하면서 개념이 또 다시 정리되었고, 복습의 효과가 있어서 개념을 기억하기 좋았던것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 매주 1블로그 포스팅으로 시작했던 나만의 소소한 프로젝트였는데..ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적인 이슈도 있어서 미뤄지다가 이제야 마무리를 할 수 있었다. 휴.. 큰 숙제 하나 끝낸 느낌이다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Swift Concurrency 정리 목록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 블로그에 정리한 Swift Concurrency 시리즈이다. 전반적으로 &lt;b&gt;기초적인 개념 위주&lt;/b&gt;로 정리되어있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://red-cherry-ring.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift Concurrency #0 - 시작하기 전에&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://red-cherry-ring.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift&amp;nbsp;Concurrency&amp;nbsp;#1&amp;nbsp;-&amp;nbsp;Async와&amp;nbsp;Await&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://red-cherry-ring.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift&amp;nbsp;Concurrency&amp;nbsp;#2&amp;nbsp;-&amp;nbsp;Structured&amp;nbsp;Concurrency와&amp;nbsp;Task&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://red-cherry-ring.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift&amp;nbsp;Concurrency&amp;nbsp;#3&amp;nbsp;-&amp;nbsp;Task&amp;nbsp;cancellation과&amp;nbsp;hierarchy&amp;nbsp;심화&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://red-cherry-ring.tistory.com/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift&amp;nbsp;Concurrency&amp;nbsp;#4&amp;nbsp;-&amp;nbsp;Actor로&amp;nbsp;Mutable&amp;nbsp;State&amp;nbsp;보호하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://red-cherry-ring.tistory.com/45&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift&amp;nbsp;Concurrency&amp;nbsp;#5&amp;nbsp;-&amp;nbsp;Concurrency와&amp;nbsp;Thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://red-cherry-ring.tistory.com/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift&amp;nbsp;Concurrency&amp;nbsp;#6&amp;nbsp;-&amp;nbsp;Data&amp;nbsp;Race&amp;nbsp;제거하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://red-cherry-ring.tistory.com/47&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swift&amp;nbsp;Concurrency&amp;nbsp;#7&amp;nbsp;-&amp;nbsp;AsyncSequence와&amp;nbsp;AsyncStream&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;추가로 공부하면 좋은 자료들&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 블로그 내용 외에도, Swift Concurrency를 더 자세히 이해하는 데 도움이 될만한 자료도 첨부한다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;WWDC Sessions&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://www.youtube.com/watch?v=bq4DVvVJ7H8&quot;&gt;WWDC21: Visualize and optimize Swift concurrency | Apple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://www.youtube.com/watch?v=Owcx2Upk6p8&quot;&gt;WWDC21: Use async-await with URLSession | Apple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://www.youtube.com/watch?v=F4qhJ5ZkU-o&quot;&gt;WWDC21: Swift concurrency: Update a sample app | Apple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://www.youtube.com/watch?v=MTGh9U2yHNM&quot;&gt;WWDC21:&amp;nbsp;Discover&amp;nbsp;concurrency&amp;nbsp;in&amp;nbsp;SwiftUI&amp;nbsp;|&amp;nbsp;Apple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://www.youtube.com/watch?v=UubThW_xQ4k&quot;&gt;WWDC21:&amp;nbsp;Bring&amp;nbsp;Core&amp;nbsp;Data&amp;nbsp;concurrency&amp;nbsp;to&amp;nbsp;Swift&amp;nbsp;and&amp;nbsp;SwiftUI&amp;nbsp;|&amp;nbsp;Apple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://www.youtube.com/watch?v=KwV23ASELQ0&quot;&gt;WWDC22: Meet distributed actors in Swift | Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;공식 문서&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/&quot;&gt;Apple Document - Concurrency&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 실제 프로젝트에 적용하면서 헷갈렸던 부분과 주의해야 했던 점들을 기록하고 마무리하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift Concurrency 테스트용으로 사용했던 GitHub repo도 첨부한다.&lt;/p&gt;
&lt;figure id=&quot;og_1772431922690&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - hobin-han/SwiftConcurrencyLab&quot; data-og-description=&quot;Contribute to hobin-han/SwiftConcurrencyLab development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/hobin-han/SwiftConcurrencyLab&quot; data-og-url=&quot;https://github.com/hobin-han/SwiftConcurrencyLab&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kLcw3/dJMb8RjZqV7/mg5tGSbsOe4pcpNREQSkF0/img.png?width=1200&amp;amp;height=600&amp;amp;face=963_153_1068_267,https://scrap.kakaocdn.net/dn/mABhu/dJMb8ZvyWI2/xgch2slvQCSGnFroQ43fLK/img.png?width=1200&amp;amp;height=600&amp;amp;face=963_153_1068_267&quot;&gt;&lt;a href=&quot;https://github.com/hobin-han/SwiftConcurrencyLab&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/hobin-han/SwiftConcurrencyLab&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kLcw3/dJMb8RjZqV7/mg5tGSbsOe4pcpNREQSkF0/img.png?width=1200&amp;amp;height=600&amp;amp;face=963_153_1068_267,https://scrap.kakaocdn.net/dn/mABhu/dJMb8ZvyWI2/xgch2slvQCSGnFroQ43fLK/img.png?width=1200&amp;amp;height=600&amp;amp;face=963_153_1068_267');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - hobin-han/SwiftConcurrencyLab&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to hobin-han/SwiftConcurrencyLab development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;withCheckedContinuation 와 Task cancel&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-pm-slice=&quot;0 0 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;orderedList&quot; data-prosemirror-content-type=&quot;node&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;listItem&quot; data-prosemirror-content-type=&quot;node&quot;&gt;withCheckedContinuation을 사용할 땐, 반드시 최소 한번 resume(succeed/failed/cancelled) 되어야 한다.&lt;br /&gt;누락되는 경우 메모리 leak이 발생한다.&lt;/li&gt;
&lt;li data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;listItem&quot; data-prosemirror-content-type=&quot;node&quot;&gt;Task는 Combine의 subscription과 달리 Task 가 deinit 되어도 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;cancel()이&lt;/span&gt;&amp;nbsp;자동 호출되지 않는다.&lt;/li&gt;
&lt;li data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;listItem&quot; data-prosemirror-content-type=&quot;node&quot;&gt;Task.isCancelled 는 Swift Concurrency 가 아닌 다른 비동기 로직 내부에서는 동작하지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;heading&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;badcd0098ec3&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span data-prosemirror-mark-name=&quot;code&quot; data-prosemirror-content-type=&quot;mark&quot;&gt;Task { }&lt;/span&gt; 로 여러번 감쌀 때에는 주의해서 사용한다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-prosemirror-mark-name=&quot;code&quot; data-prosemirror-content-type=&quot;mark&quot;&gt;Task {}&lt;/span&gt;는 structured Task 가 아니기 때문에 부모~자식 Task 구조를 갖지 않으며, 상위 Task 가 취소되어도 하위 Task 가 취소되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1772430119042&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Task {
  let value = await getValue()
  doSomething(value)
}

func doSomething(value: Any) {
  Task {
    // ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;heading&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;3c1b7d8c2cb1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;heading&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;3c1b7d8c2cb1&quot; data-ke-size=&quot;size20&quot;&gt;Task 사용시 [weak self] 가 필요할까?&lt;/h4&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;heading&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;3c1b7d8c2cb1&quot; data-ke-size=&quot;size16&quot;&gt;필요 없다. 강한 순환 참조가 발생하지 않는다.&lt;/p&gt;
&lt;p data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;heading&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;3c1b7d8c2cb1&quot; data-ke-size=&quot;size16&quot;&gt;Unstructured Task는 아래처럼 &lt;a href=&quot;https://developer.apple.com/documentation/swift/withtaskcancellationhandler(operation:oncancel:isolation:)&quot; data-prosemirror-mark-name=&quot;link&quot; data-prosemirror-content-type=&quot;mark&quot;&gt;withTaskCancellationHandler&lt;/a&gt; 를 사용해 관리해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1772430228370&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func performUnstructuredWork() async throws {
    let task = Task {
        // ... some work ...
    }

    try await withTaskCancellationHandler {
        try await task.value
    } onCancel: {
        task.cancel() // &amp;lt;- unstructured task 를 이런식으로 cancel 시켜줄 수 있다.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-prosemirror-node-block=&quot;true&quot; data-prosemirror-node-name=&quot;heading&quot; data-prosemirror-content-type=&quot;node&quot; data-local-id=&quot;d9ae36ed9f2f&quot; data-ke-size=&quot;size20&quot;&gt;Actor 전환은 최소화 한다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/hobin-han/SwiftConcurrencyLab/blob/main/Tests/ActorPerformanceTests.swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;테스트 코드&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히나 MainActor 에서의 다른 actor 로의 호핑 하는건 비용이 많이들며, MainActor가 아닌 경우에도 actor hoping이 무한히 반복되는 경우 성능이 좋지 않다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-renderer-start-pos=&quot;4891&quot;&gt;Test testActorPerformanceWithMainActorAtOnce() passed after 0.001 seconds.&lt;/li&gt;
&lt;li data-renderer-start-pos=&quot;4891&quot;&gt;Test testActorPerformanceWithCommonActorAtOnce() passed after 0.001 seconds.&lt;/li&gt;
&lt;li data-renderer-start-pos=&quot;4891&quot;&gt;Test testActorPerformanceWithCommonActorSeveralTimes() passed after 0.002 seconds.&lt;/li&gt;
&lt;li data-renderer-start-pos=&quot;4891&quot;&gt;Test testActorPerformanceWithMainActorSeveralTimes() passed after 0.030 seconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-renderer-start-pos=&quot;5219&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; MainActor로의 hoping 시도시 더 오래 걸리긴 하지만, 성능 이슈가 크진 않으므로 &lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;무한히 반복되는 구조가 아니라면&lt;/span&gt; Actor hoping 횟수 제한을 두지는 않아도 될것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Actor의 isolation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/hobin-han/SwiftConcurrencyLab/blob/main/Tests/ActorIsolationTests.swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;테스트 코드&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;Actor내의 함수들은 동시에 호출되어도 진행중인 actor 작업이 끝나야 다음 작업 실행된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Task와 Priority&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/hobin-han/SwiftConcurrencyLab/blob/main/Tests/TaskPriorityTests.swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;테스트 코드&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #292a2e; text-align: start;&quot; data-indent-level=&quot;1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Task {} 의 default priority 는 .high&lt;/li&gt;
&lt;li&gt;Task.detached {} 의 default priority 는 .medium&lt;/li&gt;
&lt;li&gt;Task {} 생성시 priority 를 직접지정하지 않으면, 상위 Task 의 priority 로 생성된다.&lt;/li&gt;
&lt;li&gt;Task {} 생성시 priority 를 직접 지정해줄 수도 있다.&lt;/li&gt;
&lt;li&gt;Task.detached {} 로 생성시에는 current Task 의 priority 와 관계없이 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #292a2e; text-align: start;&quot;&gt;MainActor 내의 nonisolated 함수 와 MainThread 보장여부&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1772431021034&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation
import Testing

@MainActor
class NonisolatedTests {
    
    @Test
    func inheritedActorContextTask() {
        Task {
            #expect(#isolation === MainActor.shared)
            
            let actor = CounterActor()
            await actor.resetSlowly(to: 10)
            
            #expect(#isolation === MainActor.shared)
        }
    }
    
    @Test
    nonisolated func nonInheritedActorContextTask() {
        Task {
            #expect(#isolation !== MainActor.shared)
            
            let actor = CounterActor()
            await actor.resetSlowly(to: 10)
            
            #expect(#isolation !== MainActor.shared)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;inheritedActorContextTask() &amp;rarr; Main Actor O&lt;/li&gt;
&lt;li&gt;nonInheritedActorContextTask() &amp;rarr; Main Actor X&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Swift</category>
      <category>SWiFT</category>
      <category>swift concurrency</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/48</guid>
      <comments>https://red-cherry-ring.tistory.com/48#entry48comment</comments>
      <pubDate>Mon, 2 Mar 2026 15:00:26 +0900</pubDate>
    </item>
    <item>
      <title>Swift Concurrency #7 - AsyncSequence와 AsyncStream</title>
      <link>https://red-cherry-ring.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=sPH4S1EPz-0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WWDC21: Meet AsyncSequence | Apple&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=sPH4S1EPz-0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/emhmBW/dJMb9bvZMWa/VMNuI0ZGkdKd7NkhLUdfw1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/b43pis/dJMb9frCWlw/TULdPeXQafOPelOSB5QCi0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/5kcSV/dJMb9efbqIf/H9EQ89choMslTKSLgbkTR1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;WWDC21: Meet AsyncSequence | Apple&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/sPH4S1EPz-0&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AsyncSequence란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/asyncsequence&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;AsyncSequence&lt;/b&gt;&lt;/a&gt;는 본질적으로&lt;b&gt; async가 적용된 Sequence&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Sequence&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;일반 Sequence와 마찬가지로 정해진 순서를 가진다. 순서가 뒤바뀌거나 임의로 접근(random access)하는 것은 불가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Suspend &amp;amp; Resume&lt;/b&gt;: 각각의 요소(element)에 대해 작업을 일시 중단(suspend)하고, iterator가 값을 생성하거나 에러를 방출하면 다시 재개(resume)된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에러 처리와 종료&lt;/b&gt;: 비동기 작업을 수행하므로 에러가 발생할 수 있다. 컴파일러는 이를 일반 throwing 함수와 동일하게 처리한다. 에러가 발생하거나 더 이상 데이터가 없으면 iterator.next()는 nil을 반환하며 종료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사용 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;표준 방식&lt;/b&gt;: for-await-in 또는 for-try-await-in(에러 발생 가능성이 있는 경우) 구문을 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;iterator 사용하기&lt;/b&gt;: makeAsyncIterator()를 통해 명시적으로 iterator를 생성하고, while let quake = await iterator.next() 방식으로 순회할 수도 있다.&lt;/li&gt;
&lt;li&gt;일반 for문과 동일하게 break나 continue를 사용하여 반복문을 종료하거나 스킵할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772424622007&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 표준 방식
for await quake in quakes {
    if quake.magnitude &amp;gt; 3 {
        displaySignificantEarthquake(quake)
    }
}

// iterator 방식
var iterator = quakes.makeAsyncIterator()
while let quake = await iterator.next() {
    if quake.magnitude &amp;gt; 3 {
        displaySignificantEarthquake(quake)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;무기한 AsyncStream&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 AsyncSequence를 동시에 순회해야 하거나, 무기한으로 실행될 가능성이 있는 스트림을 다룰 때는 &lt;b&gt;Task로 감싸서(encapsulate) 실행&lt;/b&gt;하면 된다. 생성한 Task를 변수에 저장해 두면 추후 외부에서 .cancel()을 호출하여 작업을 안전하게 중단시킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1772424995812&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let quakeTask = Task {
    for await quake in quakes {
        if quake.magnitude &amp;gt; 3 {
            displaySignificantEarthquake(quake)
        }
    }
}

// 나중에...
quakeTask.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AsyncStream 사용처 및 제공 API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 복잡했던 비동기 데이터 처리를 AsyncStream을 이용해 손쉽게 처리할 수 있도록 Apple에서는 다양한 표준 API를 제공한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FileHandle&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/filehandle/bytes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;public&amp;nbsp;var&amp;nbsp;bytes:&amp;nbsp;AsyncBytes&lt;/a&gt;&lt;br /&gt;파일에서 바이트를 비동기적으로 읽어온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1772425756983&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;bytes | Apple Developer Documentation&quot; data-og-description=&quot;The file&amp;rsquo;s contents, as an asynchronous sequence of bytes.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/foundation/filehandle/bytes&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/foundation/filehandle/bytes&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3WG1l/dJMb8QejGzI/pe6NS60cWZ8hkMGL7BOhE1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cHUDUc/dJMb8T9WWKS/Vkhv60CU4NXeuMkGZYrP5k/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/filehandle/bytes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/filehandle/bytes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3WG1l/dJMb8QejGzI/pe6NS60cWZ8hkMGL7BOhE1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cHUDUc/dJMb8T9WWKS/Vkhv60CU4NXeuMkGZYrP5k/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;bytes | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The file&amp;rsquo;s contents, as an asynchronous sequence of bytes.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1772425178913&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for try await line in FileHandle.standardInput.bytes.lines {
    /* 생략 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;URL&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0e0e0e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/url/resourcebytes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;public var resourceBytes: AsyncBytes&lt;/a&gt;&lt;br /&gt;URL에서 데이터를 바이트 단위로 읽어온다.&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/url/lines&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;public var lines: AsyncLineSequence&amp;lt;AsyncBytes&amp;gt;&lt;/a&gt;&lt;br /&gt;URL에서 데이터를 줄 단위로 읽어온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772425439302&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let url = URL(fileURLWithPath: &quot;/tmp/somefile.txt&quot;)

for try await line in url.lines {
    /* 생략 */	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;URLSession&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 요청 시 데이터를 바이트 스트림으로 받는다. 응답과 비동기 바이트 시퀀스를 함께 리턴받아 사용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0e0e0e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;func bytes(from: URL) async throws -&amp;gt; (AsyncBytes, URLResponse)&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;3&quot;&gt;func bytes(for: URLRequest) async throws -&amp;gt; (AsyncBytes, URLResponse)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772425488066&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let (bytes, response) = try await URLSession.shared.bytes(from: url)

guard let httpResponse = response as? HTTPURLResponse,
    httpResponse.statusCode == 200 else {
    throw MyNetworkingError.invalidServerResponse
}

for try await byte in bytes {
    /* 생략 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Notifications&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Notification을 비동기 시퀀스로 대기하며 처리할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;public func notificatoins(named: Notification.Name, object: AnyObject) -&amp;gt; Notifications&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772425609415&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let center = NotificationCenter.default

let notification = await center.notifications(named: .NSPersistentStoreRemoteChange).first {
    $0.userInfo[NSStoreUUIDKey] == storeUUID
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ AsyncSequence 값 가공하기 (Manipulating)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Sequence를 다룰 때 사용하던 고차 함수들을 AsyncSequence에서도 그대로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원 함수 예시:&lt;br /&gt;allSatisfy, max, prefix, joined, dropFirst, flatMap, zip, compactMap, min, reduce, filter, contains, first 등..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AsyncSequence 도입하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 콜백(Callback)이나 델리게이트(Delegate) 패턴으로 작성된 코드는&amp;nbsp;&lt;b&gt;AsyncStream를 사용&lt;/b&gt;하면 AsyncSequence 형태로 변환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선&amp;nbsp;&lt;a href=&quot;https://developer.apple.com/documentation/swift/asyncstream&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AsyncStream&lt;/a&gt;을 어떻게 사용하는지 알아보면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1772426196280&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct AsyncStream&amp;lt;Element&amp;gt;: AsyncSequence {
    public init(_ elementType: Element.Type = Element.self,
                maxBufferedElements limit: Int = .max,
                _ build: (Continuation) -&amp;gt; Void)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 방출&lt;/b&gt;: &lt;a href=&quot;https://developer.apple.com/documentation/swift/asyncstream/continuation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AsyncStream.Continuation&lt;/a&gt;을 사용해 continuation.yield(값) 형태로 핸들러가 호출될 때마다 데이터를 방출한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정상 종료/에러 방출&lt;/b&gt;: continuation.finish()를 호출하면 iterator가 nil을 생성하여 시퀀스를 종료한다. (별도의 finish 호출이 없는 경우 Task가 취소될 때까지 무한 수신)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버퍼링 지원&lt;/b&gt;: 버퍼가 가득 차면 이전 데이터를 버리는 등의 처리 방식을 자체적으로 지원한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Task 취소&lt;/b&gt;: Task가 취소되면 continuation.onTermination&amp;nbsp;클로저가 호출된다. 클로저의 &lt;a href=&quot;https://developer.apple.com/documentation/swift/asyncthrowingstream/continuation/termination&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;termination&lt;/a&gt; 파라미터를 통해 finished/cancelled 상태 확인이 가능하며, 해당 클로저를 통해 네트워크나 타이머 등을 안전하게 중단(Cleanup)시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 코드가 아래와 같았다면,&lt;/p&gt;
&lt;pre id=&quot;code_1772425853811&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class QuakeMonitor {
    var quakeHandler: (Quake) -&amp;gt; Void
    func startMonitoring()
    func stopMonitoring()
}

// ------

let monitor = QuakeMonitor()
monitor.quakeHandler = { quake in
	...
}

monitor.startMonitoring()
...
monitor.stopMonitoring()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AsyncStream을 사용하면 아래로 변환할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1772425963217&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let quakes = AsyncStream(Quake.self) { continuation in
    let monitor = QuakeMonitor()
    monitor.quakeHandler = { quake in
        continuation.yield(quake) // 핸들러 호출될 때마다 element 방출
    }
    continuation.onTermination = { _ in
        monitor.stopMonitoring() // onTermination 에서 cleanup 해주기
    }

    monitor.startMonitoring()
}

// ------
// 아래와 같이 사용할 수 있다.

let significantQuakes = quakes.filter { quake in
    quake.magnitude &amp;gt; 3
}
for await quake in significantQuakes {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  에러 처리가 필요한 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러를 방출해야 하는 커스텀 시퀀스를 만들 때는 &lt;b&gt;AsyncThrowingStream&lt;/b&gt;을 사용하며, 호출부에서는 for-try-await-in을 사용해 에러를 안전하게 처리(do-catch)하면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유익했다면 댓글/공감 남겨주세요~~ 작성자에게 큰 힘이 됩니다 ☺️&lt;/p&gt;</description>
      <category>Swift</category>
      <category>AsyncSequence</category>
      <category>AsyncStream</category>
      <category>SWiFT</category>
      <category>swift concurrency</category>
      <category>WWDC21</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/47</guid>
      <comments>https://red-cherry-ring.tistory.com/47#entry47comment</comments>
      <pubDate>Mon, 2 Mar 2026 13:47:11 +0900</pubDate>
    </item>
    <item>
      <title>Swift Concurrency #6 - Data Race 제거하기</title>
      <link>https://red-cherry-ring.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/xFvZ1CwEFnI?si=Rn6t2BubOEA3JGpk&quot;&gt;WWDC22: Eliminate data races using Swift Concurrency | Apple&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/xFvZ1CwEFnI&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 위 영상에서 예측하기 어려운 동시성(Concurrency)의 세계를 &lt;b&gt;바다&lt;/b&gt;에 비유하여 설명한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;⛵️ &lt;b&gt;Task (배):&lt;/b&gt; 바다를 항해하는 배로, 독립적으로 자신의 할 일을 수행.&lt;/li&gt;
&lt;li&gt; ️ &lt;b&gt;Actor (섬):&lt;/b&gt; 바다 위 고립된 섬으로, 한 번에 하나의 배(Task)만 접근할 수 있는 안전한 데이터 보관소.&lt;/li&gt;
&lt;li&gt;  &lt;b&gt;Value Type (파인애플):&lt;/b&gt; 복사해서 다른 배로 넘겨줄 수 있는 안전한 데이터 구조체.&lt;/li&gt;
&lt;li&gt;  &lt;b&gt;Reference Type (닭):&lt;/b&gt; 복사본이 아닌 원본 참조를 공유하게 되어 혼란(Data Race)을 유발할 수 있는 클래스 객체.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Task와 데이터 격리 (Task Isolation)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task는 독립적인 실행 컨텍스트(Context)를 가지며, 다음 세 가지 특징을 갖는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Sequential:&lt;/b&gt; 시작부터 끝까지 순차적으로 코드를 수행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Asynchronous:&lt;/b&gt; 비동기적이며, 언제든 일시 중단(suspend)될 수 있고, 언제 완료될지 모른다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Self-contained:&lt;/b&gt; 각 Task는 고유한 리소스를 가지며 각 Task내에서 독립적으로 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Task에서 Task로의 데이터 전달&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task 간에   &lt;b&gt;Value Type(값 타입)&lt;/b&gt; 데이터를 전달하면 실제로는 복사본(copy)이 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다른 Task의 리소스에 영향을 주지 않게되어 &lt;b&gt;Data Race 안전성을 보장&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;(* 참고 : 이 때문에 Swift 에서는 가능하면 Value Type으로 구현하기를 권장한다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Reference Type(참조 타입)&lt;/b&gt;을 넘기면 참조 주소만 전달되기 때문에, 여러 Task가 동시에 데이터를 수정하게 되면 &lt;b&gt;공유 가변 상태(Shared mutable data) 문제가 발생&lt;/b&gt;하게 된다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Data Race를 유발하기 딱 좋은 상태라는 의미이다. 이럴 때, Data Race를 방지하기 위해 등장한게 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;Sendable&lt;/b&gt; 이다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sendable 프로토콜: 안전한 데이터 공유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 &lt;b&gt;Sendable&lt;/b&gt;은 컴파일 단계에서 Reference Type 데이터가 무분별하게 Task 간에 공유되지 않도록 제한한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sendable 프로토콜을 채택함으로써 여러 &lt;b&gt;Task(Isolation Domain) 사이에서 안전하게 주고받을 수 있는 데이터임을 명시&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Sendable이 되려면 아래의 케이스들 중 하나일때만 가능한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Value Type (struct, enum):&lt;br /&gt;&lt;/b&gt;내부의 모든 데이터가 Sendable이면 암시적으로 Sendable을 만족한다.&lt;br /&gt;(Array도 Element가 Sendable이면 만족)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reference Type (Class):&lt;br /&gt;&lt;/b&gt;가변 상태를 가지므로 원칙적으로 불가능하다. 하지만 아래의 경우에는 예외적으로 만족한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불변 데이터(let)만 있는 final class인 경우&lt;/li&gt;
&lt;li&gt;내부적으로 Lock을 구현하여 직접 Data Race를 방지한 경우, &lt;b&gt;@unchecked Sendable&lt;/b&gt;로 명시가 가능하다.&lt;br /&gt;(@unchecked Sendable로 명시하면 컴파일 단계에서 Sendable 에러가 발생하지 않게 된다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1772337553566&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum Ripeness: Sendable { ✅
	case hard
	case perfect
	case mushy(daysPast: Int)
}

struct Pineapple: Sendable { ✅
	var weight: Double
	var ripeness: Ripeness
}

struct Crate: Sendable { ✅
	var pineapples: [Pineapple]
}

struct Coop: Sendable {  
	var flock: [Chicken] // non-sendable state!!
}

class ConcurrentCache&amp;lt;Key: Hashable &amp;amp; Sendable, Value: Sendable&amp;gt;: @unchecked Sendable {
	var lock: NSLock
	var storage: [Key: Value]
    
    /* NSLock을 이용해 개발적으로 Data Race 방지 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Task 초기화에서의 Sendable 체크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Task 의 operation closure 는 @Sendable 로 선언되어있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772337858827&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Task&amp;lt;Success: Sendable, Failure: Error&amp;gt; {
	static func detached(
		priority: TaskPriority? = nil,
		operation: @Sendable @escaping () async throws -&amp;gt; Success
	) -&amp;gt; Task&amp;lt;Success, Failure&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;일반적으로 함수 타입은 프로토콜을 채택할 수 없지만, 함수 타입에서도 @Sendable로 선언이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;함수가 Sendable로 선언되었다는 건&amp;nbsp;&lt;b&gt;함수의 value 가 다른 isolation domain(= Task)으로 전달될 수 있다는 의미&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;따라서 Task 를 별도로 생성할 때에도 데이터를 전달하려면 Sendable 타입이어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772337883240&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let lily = Chicken(name: &quot;Lily&quot;) // should be Sendable!
Task.detached { // `@Sendable` clousure 이기 때문에
	lily.feed()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Task에서 데이터를 return하는 경우에도 Sendable 타입이어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1772337772459&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let petAdoption = Task { // type 'Chicken' should be Sendable
	let chickens = await hatchNewFlock()
	return chickens.randomElement!
}
let pet = await petAdoption.value&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Actor: 안전한 상태 격리 (Actor Isolation)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명..! Swift Concurrency 적용하다보면 Task 간에 참조 타입 데이터를 주고받고 싶은데... 이미 클래스엔아 가변 데이터가 존재하고, 내부적으로 data race를 방지하는 로직도 없는 경우가 있을 것이다. 이럴 때 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt; ️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Actor&lt;/b&gt;를 사용한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;Actor 자체는 참조 타입이지만, 아래 특성들을 통해 내부적으로 data race를 방지할 수 있으며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Actor는 묵시적으로 Sendable을 만족&lt;/b&gt;한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Actor는 내부의 모든 가변 데이터와 메서드 접근을 격리한다.&lt;br /&gt;= &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Actor 는 Reference Type이지만 concurrent 접근에 대해 모든 프로퍼티와 코드를 보장한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;non-sendable 데이터는 Actor와 Task 간에서 주고 받을 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Actor Reference Isolation&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 상황에서 Actor Isolation이 어떻게 동작하는지 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 이전에 배웠듯이 Task는 actor context를 상속하고, Task.detached는 actor context를 상속하지 않는 특성이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1772339152903&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actor Island {
    var flock: [Chicken] //  ️
    var food: [Pineapple] //  ️

    func advanceTime() { //  ️
        //  ️ Island actor의 isolated context
        let totalSlices = food.indices.reduce(0) { total, nextIndex in
            total + food[nextIndex].slice()
        }

        Task { //  ️ Task 는 actor context를 상속
            flock.map(Chicken.produce)
        }

        Task.detached { // ⛵️ Task.detached는 actor context를 상속하지 않음
            // ❗️ 따라서 food 에 접근할 때 await 키워드 필요
            let ripePineapples = await food.filter { $0.ripeness == .perfect }
            print(&quot;There are \(ripePineapples.count) ripe pineapples on the island&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Island는 actor로 구현되어있기 때문에 내부의 모든 가변 데이터와 메서드 접근을 격리한다.&lt;/li&gt;
&lt;li&gt;advanceTime 함수가 호출되면, 그 함수 내부 코드 또한 격리된 actor context에서 실행된다.&lt;/li&gt;
&lt;li&gt;Task는 현재의 actor context를 상속하기 때문에 클로저 내부에서도 isolation을 유지하며, 클로저에서 actor isolated 데이터인 flock 프로퍼티에 접근할 수 있다.&lt;/li&gt;
&lt;li&gt;하지만 Task.detached는 actor context를 상속하지 않기 때문에 클로저 내부에서 actor isolated 데이터인 flock 프로퍼티에 접근하려고 한다면, await 키워드를 사용해 isolated context에 접근해야만 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Non-isolated&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nonisolated&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;키워드는&amp;nbsp;&lt;/span&gt;Actor의 보호가 필요 없는 독립적인 함수에 명시하며, 외부에서도 await 없이 즉시 호출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nonisolated 함수 내부에서 actor context에 접근할 땐, await 키워드를 사용해야 접근이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, nonisolated async 코드는 항상 Cooperative Pool에서 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제껏 Actor에 관해 배운 내용을 정리하면, 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각각의  ️ Actor 인스턴스는 완전히 독립적으로 동작한다.&lt;/li&gt;
&lt;li&gt;한번에 하나의 ⛵️Task만 actor로&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;접근해 실행/처리가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Actor에 진입하고 나올 때마다 Sendable 체크가 발생한다.&lt;/li&gt;
&lt;li&gt;Actor는 그 자체로 Sendable 하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@MainActor: UI를 담당하는 특별한 섬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@MainActor는 UI 작업을 위해 &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;MainThread&lt;/span&gt;에서 실행되는 유일한 Actor이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰(View)나 뷰 컨트롤러(ViewController)와 같은 수많은 UI 작업이 이곳에서 수행되어야 한다.&lt;/li&gt;
&lt;li&gt;일반 Actor처럼 한 번에 하나의 업무만 수행하므로, &lt;b&gt;너무 오래 걸리는 작업을 MainActor에서 수행하면 UI 응답이 지연(멈춤)될 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;@MainActor function/closure&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;특정 함수나 클로저에만 @MainActor 속성을 지정하여 해당 코드 구간만 메인 스레드에서 실행되도록 격리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772414504104&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수 에 @MainActor 지정 가능
@MainActor func updateView() {...}

// closure 에 @MainActor 지정 가능
Task { @MainActor in
    // ...
    view.selectedChicken = lily
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;예를 들어 non-isolated 함수에서 @MainActor 로 지정된 함수 호출하게 되면, 반드시 MainActor로 진입되므로 await 키워드가 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772414590407&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 어떤 Actor에도 격리되지 않은 외부 비동기 함수
nonisolated func computeAndUpdate() async { 
    computeNewValues()
    await updateView() // MainActor로 진입해야 하므로 await 필수!
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;@MainActor Type&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스(Class)나 구조체(Struct) 같은 타입에 @MainActor 속성을 지정할 수 있다. 이렇게 지정된 타입으로 생성된 인스턴스는 그 자체가 'UI를 담당하는 거대한 섬(MainActor)'에 완전히 소속되어 메인 스레드에서만 실행된다.&lt;/p&gt;
&lt;pre id=&quot;code_1772415058007&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 클래스 전체를 MainActor 섬에 격리 (자동으로 Sendable 만족)
@MainActor class ChickenViewModel {
    var flock: [Chicken] = [] // MainActor 내에서만 접근 가능
    
    func updateFlock() {
        // UI 업데이트 로직 (항상 메인 스레드에서 안전하게 실행됨)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  실무 활용 팁&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 특성 덕분에 @MainActor는 앱의 UI 뷰(View), 뷰 컨트롤러(ViewController) 그리고 뷰모델(ViewModel)을 설계할 때 매우 유용할듯 하다. 예를 들어 뷰모델을 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;@MainActor로 지정하고&lt;/span&gt; 네트워크 요청 같은 무거운 비동기 작업을 구현하면, 네트워크 통신 결과를 UI에 반영할 때 별도의 스레드 전환 처리 없이도 안전하게 메인 스레드에서 UI 업데이트를 할 수 있을 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원자성(Atomicity)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원자성은 Actor&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;는 한번에 하나의 Task 만 수행한다는&amp;nbsp;&lt;/span&gt;Actor의 특성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;A&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;ctor 내에서 실행되던&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Task&lt;/span&gt;가 await을 만나면 다른 task 가 수행될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;이 때, 아래 예시와 같은 상황에서 주의할 점이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772415213970&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 잘못된 접근 (외부에서 수정 시도 -&amp;gt; 컴파일 에러 발생)
nonisolated func deposit(pineapples: [Pineapple], onto island: Island) async {
    var food = await island.food  // 1차 접근 (읽기)
    food += pineapples            // (이 틈에 해적이 들어와서 원래 섬의 파인애플을 훔쳐갈 수 있음!)
    await island.food = food      // 2차 접근 (쓰기) - 컴파일러가 에러로 차단함!
}&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 두 번째 await을 실행하기 전에 &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;island.food 값이 변경되었다면,&lt;/span&gt; &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;두 번째 await을 실행했을 때&lt;/span&gt; 이전 계산 값을 덮어써버리는 이슈가 발생하게 된다. 즉, &lt;b&gt;논리적 데이터 레이스(High-level Data Race)&lt;/b&gt;가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 컴파일러는 이러한 위험을 원천 차단하기 위해, 비격리 컨텍스트(non-isolated)에서는 Actor의 격리된 프로퍼티를 외부에서 마음대로 수정하지 못하도록 컴파일 에러를 발생시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;actor-isolated 프로퍼티를 수정하려면 아래처럼 isolated context (즉, actor 내에서) 수정이 이루어져야한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1772415555491&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ⭕️ 올바른 접근 (Actor 내부의 동기 함수로 분리)
extension Island {
    func deposit(pineapples: [Pineapple]) { // isolated 컨텍스트 (중단점 없음)
        var food = self.food
        food += pineapples
        self.food = food
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;실행 순서 (Ordering)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종종 정해진 순서대로 이벤트를 처리해야하는 경우가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0e0e0e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;0&quot;&gt;유저 인터랙션을 발생 순서대로 처리하거나&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;서버 response를 받은 순서대로 처리하고 싶은 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Actor는 우선순위 역전을 방지하기 위해 높은 우선순위 작업을 먼저 처리하므로&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&quot;Serial Dispatch Queue(FIFO 보장)&quot;처럼&amp;nbsp;&lt;/span&gt;선입선출(FIFO)을 보장하지는 않는다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Swift Concurrency에서 각각의 이벤트가 발생한 순서대로 처리되면 좋겠을 땐 어떻게 처리할 수 있을까?&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 는 이 기능을 지원하기 위해 다양한 tool 을 제공하고 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;0&quot;&gt;Task 이용하기 (Task 는 언제나 sequential 하게 동작한다)&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;AsyncStream &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;(for await event in stream)&lt;/span&gt; 사용하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이벤트가 순서대로 처리되길 원한다면 Actor보다는 &lt;/span&gt;&lt;b&gt;Task&lt;/b&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;를 이용하거나 &lt;/span&gt;&lt;b&gt;AsyncStream&lt;/b&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;을 활용하는 것이 적합하다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Data Race 완전 방지를 위한 점진적 검사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift는 작업 중단을 막기 위해 Data Race 안전성 검사를 점진적으로 적용하도록 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Swift 5.7에서는 아래 옵션들 중 하나를 선택해 Sendability 를 얼마나 강하게 검토할건지를 직접 지정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Minimal:&lt;br /&gt;&lt;/b&gt;Swift 5.5, 5.6과 동일하며 명시적인 시도에 대해서만 진단한다. (오류가 아닌 경고 표기)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Targeted:&lt;br /&gt;&lt;/b&gt;async/await, actor 등을 이미 채택한 코드를 중심으로 검사한다. 아직 업데이트되지 않은 외부 프레임워크에서 발생하는 경고는 &lt;b&gt;@preconcurrency import&lt;/b&gt;를 사용하여 임시로 무시할 수도 있다. (&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;하지만 경고를 무시하기 위한 임시방편 이다)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;하지만 추후 framework 가 Sendable 을 지원하게 되면 @preconcurrency 를 제거해주어야 한다. 제거하지 않으면 다시 경고가 발생된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Completed:&lt;br /&gt;&lt;/b&gt;Swift 6을 가정한 완전한 검사 모드로, 프로그램 전반의 잠재적인 Data Race를 모두 잡아낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  Apple의 권장 사항:&lt;/b&gt; 한 번에 하나씩 모듈의 검사 단계를 높여가며 적용하고, 준비되지 않은 외부 모듈은 &lt;b&gt;@preconcurrency&lt;/b&gt;로 대처하며 점진적으로 마이그레이션하기&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유익했다면 댓글/공감 남겨주세요~~ 작성자에게 큰 힘이 됩니다 ☺️&lt;/p&gt;</description>
      <category>Swift</category>
      <category>actor</category>
      <category>data race</category>
      <category>nonisolated</category>
      <category>preconcurrency</category>
      <category>sendable</category>
      <category>SWiFT</category>
      <category>swift concurrency</category>
      <category>WWDC22</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/46</guid>
      <comments>https://red-cherry-ring.tistory.com/46#entry46comment</comments>
      <pubDate>Mon, 2 Mar 2026 11:46:51 +0900</pubDate>
    </item>
    <item>
      <title>Swift Concurrency #5 - Concurrency와 Thread</title>
      <link>https://red-cherry-ring.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=f-re0WpYzZo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WWDC21:&amp;nbsp;Swift&amp;nbsp;concurrency:&amp;nbsp;Behind&amp;nbsp;the&amp;nbsp;scenes&amp;nbsp;|&amp;nbsp;Apple&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=f-re0WpYzZo&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dZsKX6/dJMb8QL9skB/JzSN23nZMLVDAfY7sX3dkk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/RuDTL/dJMb86OZiKH/z5BL7Ov8TaC0RgNw5BWJj0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;WWDC21: Swift concurrency: Behind the scenes | Apple&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/f-re0WpYzZo&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 해당 포스팅은 기존 Concurrency 영상 리뷰 중 역대급으로 내용이 복잡하고 어렵다...!&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이전 포스팅을 읽지 않았다면 정독하고 오기를 권장하고, 당장 복잡한 내용을 이해하기 싫다면 스킵하는 것도 나쁘지 않다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;미리 알면 좋은 운영체제 상식&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CPU는 1개 이상의 코어로 이루어져 있다. (멀티 코어 CPU = 하나의 CPU에 여러개의 코어를 갖는 CPU)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1개의 코어는 1개의 Thread를 실행할 수 있다. (hyper&amp;nbsp;threading&amp;nbsp;기술로&amp;nbsp;1코어당&amp;nbsp;스레드&amp;nbsp;2개씩&amp;nbsp;돌릴&amp;nbsp;수&amp;nbsp;있긴한데..&amp;nbsp;여기선&amp;nbsp;배제한다)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CPU는 1개 이상의 코어로 가지며, 코어의 갯수는 CPU 칩에 따라 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Apple watch의 코어 갯수는 2개이다.&lt;/li&gt;
&lt;li&gt;iPhone은 6개의 코어를 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Serial Queue vs Concurrent Queue&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(sync/async 랑은 다른 개념이다.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Serial Queue&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;하나의 스레드&lt;/b&gt;가 큐 구조에 작업을 하나씩 적재하고, 그 큐에서 하나씩 꺼내서 작업을 실행시킨다.&lt;/li&gt;
&lt;li&gt;반드시 순서대로(큐 구조 = FIFO) 호출되기 때문에 mutual exclusion(= 상호 배제)를 지원한다.&lt;/li&gt;
&lt;li&gt;스레드 안전성을 확보할 때 사용한다.&lt;/li&gt;
&lt;li&gt;예시) 데이터베이스에 write 할 때 (write 작업은 반드시 exclusive 해야한다. 특히 write이 오래 걸리는 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Concurrent Queue&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;여러개의 스레드&lt;/b&gt;를 동시에 수행한다.&lt;/li&gt;
&lt;li&gt;Serial Queue 와 마찬가지로 큐 구조이기는 하나 여러개의 스레드에서 접근이 가능한 상황이기 때문에 먼저 호출된 함수가 먼저 실행됨을 보장하지 못한다. 즉, mutual exclusion(= 상호 배제)를 지원하지 않는다.&lt;/li&gt;
&lt;li&gt;데이터에 대한 동시 접근 문제가 없고, 여러 작업을 병렬로 실행해 성능을 최적화할 때 사용한다.&lt;/li&gt;
&lt;li&gt;예시) 여러개의 이미지를 동시에 다운로드&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GCD를 사용한 Serial/Concurrent Queue 사용법&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.124%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 38.6821%;&quot;&gt;Serial(직렬)&lt;/td&gt;
&lt;td style=&quot;width: 45.1938%;&quot;&gt;Concurrent(병렬)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.124%;&quot;&gt;Main Thread&lt;/td&gt;
&lt;td style=&quot;width: 38.6821%;&quot;&gt;DispatchQueue.main&lt;/td&gt;
&lt;td style=&quot;width: 45.1938%;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.124%;&quot;&gt;Global Thread&lt;/td&gt;
&lt;td style=&quot;width: 38.6821%;&quot;&gt;DispatchQueue(label: &quot;&quot;)&lt;/td&gt;
&lt;td style=&quot;width: 45.1938%;&quot;&gt;DispatchQueue(label: &quot;&quot;, attributes: .concurrent)&lt;br /&gt;DispatchQueue.global(qos: .default)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;(참고) Global Concurrent Queue 비교&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;DispatchQueue(label: &quot;&quot;, attributes: .concurrent)&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;: 특정 작업들을 label 단위로 그룹핑할 때, 주로 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;DispatchQueue.global(qos: .default)&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;: 범용적인 백그라운드 작업을 할 때, 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;sync vs async&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;sync&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업이 전부 끝날때까지 기다린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;async&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업이 block 되면 다른 작업을 한다. block 된 작업은 추후 언젠지 모르는 시점에 완료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Serial / Concurrent Queue 와 sync / async&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Serial Queue&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Concurrent Queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;sync&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;스레드 안정성 및 작업 순서 보장&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;(거의 사용하지 않음)&lt;br /&gt;모든 작업의 완료를 기다림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;async&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;작업의 순차 실행&lt;br /&gt;&lt;br /&gt;* 하지만 완료되는 순서가 보장되진 않는다.&lt;br /&gt;* 스레드 안정성 및 작업 순서 보장되지 않음.&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;여러 작업 동시 실행 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;serial / concurrent&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;몇개의 스레드로 실행을 하느냐 (작업 시작 순서가 보장되느냐)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;sync / async&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업 완료를 기다리느냐 마느냐 (작업 완료 순서가 보장되느냐)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;GCD 와 Concurrency 비교&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비교를 쉽게하기 위해 아래를 구현해야하는 상황이라고 가정해보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;버튼을 클릭하면 리스트 데이터를 가져와야 한다.&lt;/li&gt;
&lt;li&gt;단, Database에 리스트 데이터가 없다면, 리스트 데이터(GET API)를 서버에 요청한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수신된 데이터는 Database에 write한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Database에 리스트 데이터가 있다면, Database에서 read한다.&lt;/li&gt;
&lt;li&gt;데이터는 UI에 반영한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Grand Central Dispatch(= GCD) 사용해서 구현하기&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GCD를 사용하면, 아래와 같이 구현될 수 있다.&lt;br /&gt;(왜 이렇게 구현되었는지 까지는 자세히 알 필요 없고, 단순히 아래처럼 구현되었음을 가정하자)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;버튼 클릭 이벤트는 Main Serial Queue에서 수신된다.&lt;/li&gt;
&lt;li&gt;API 호출은 Global Concurrent Queue(async)에서 실행한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Database write은 Global Serial Queue(sync)에서 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Database read는 Global Serial Queue(async)에서 실행한다.&lt;/li&gt;
&lt;li&gt;UI 업데이트는 Main Serial Queue(async)에서 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대략적인 구현된 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1771900086082&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// 2
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: concurrentQueue)
let dataTask = urlSession.dataTask(with: url) { data, response, error in
    guard let data else { return }
    do {
        let list = try deserializeList(from: data)
        // 2-1
        databaseQueue.sync {
            updateDatabase(with: list)
        }
    } catch { /* ... */ }
}
dataTask.resume()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;⚠️ 하지만 이렇게 구현하면, Thread Explosion이 발생할 수 있다..!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 Concurrent Queue(= 여러 스레드 사용가능)에 등록된 작업이 많을 때, 스레드가 어떻게 동작하는지 알아야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;➡️&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;async 작업중 세마포어/lock와 같은 이유로 작업중이던 스레드가 block되면,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 스레드를 돌리던 CPU코어는 block된 스레드를 따로 저장하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;새로운 스레드를 생성&lt;/b&gt;해 다른 작업을 진행한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 위의 코드는 무엇이 문제일까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2-1 작업과 같이 Database에 write하기 위해 Global Serial Queue(sync)로 접근하고 있는데 이 작업으로 스레드가 block될 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 위에서 미리 알아봤듯이 block될 때마다 CPU는 새 스레드를 생성해 다른 작업을 하게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 위 코드가 동시에 여러번 실행된다면 어떻게 될까?&lt;br /&gt;(예를 들어 100개의 url이 있고, 반복문을 사용해 모든 url에 대해 위 코드를 실행한다면?)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아이폰 CPU에는 6개의 코어가 존재하므로, 약 16배(≒ 100/6)나 많은 스레드를 생성하게 되는데, 이것이 바로 Thread Explosion이다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Thread Explosion(스레드 폭발)&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;: 시스템이 CPU 코어갯수 보다 더 많은 스레드로 overcommitted된 상태.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문제점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;deadlock(교착상태) 유발
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일부 스레드가 lock된 상태에서, CPU 점유율이 높아 다른 스레드가 실행될 수 없다면,&amp;nbsp; CPU 작업은 더이상 진전되지 못하고 영구적으로 unlock되지 못할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;성능 저하
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 오버 헤드 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;block된 각각의 스레드는 stack에 저장된다.&lt;/li&gt;
&lt;li&gt;block된 스레드를 추적하기 위해서는 커널 데이터를 사용해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스케줄링 오버 헤드 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 코어 내에서 작업 스레드가 변경될 때마다 thread context switch가 발생하는데,&lt;br /&gt;과한 context switch는 스케쥴링 지연을 유발한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Concurrency 사용해서 구현하기&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GCD에서 발생할 수 있는 Thread Explosion을 보완한 방식이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GCD에서는 스레드를 계속 생성할 수 있었다면,&lt;/li&gt;
&lt;li&gt;Concurrency는 CPU 코어 갯수 만큼의 스레드만 존재&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;한다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;thread&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;context switch가 없는 모델 사용)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Concurrency는 Thread Context Switch를 하는 대신 하나의 스레드 안에서 continuation을 switch 하도록 구현되었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;(성능상&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;continuation switch가 thread switch보다 훨씬 가볍다. 함수 호출 비용 만큼만 소요된다.)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;b&gt;단, 스레드가 block되지 않을 것임이 반드시 보장되어야 한다!&lt;/b&gt;&lt;br /&gt;(스레드 block을 방지하기위해 지금의 Swift Concurrency 디자인이 되었다)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;(이러한 보장을 runtime contract 라고 한다)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;GCD에서 구현된 로직을 Concurrency로 구현하면 아래와 같다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #0e0e0e; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;0&quot;&gt;2번, 2-1번 작업을 async 함수로 변경하고,&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;for문을&lt;span&gt;&amp;nbsp;&lt;/span&gt;withThrowingTaskGroup&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 감싸고,&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;변경된 async 함수를&lt;span&gt;&amp;nbsp;&lt;/span&gt;group.async&lt;span&gt;&amp;nbsp;&lt;/span&gt;내부에서 호출하면 된다.&lt;br /&gt;(async-await을 사용하면서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;serial/concurrent 와 async/sync 구분없이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용하게 됨)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1771900086084&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;await withThrowingTaskGroup(of: [Article].self) { group in
	for feed in feedsToUpdate {
		group.async {
			// 2
			let (data, response) = try await URLSession.shared.data(from: feed.url)
			let articles = try deserializeArticles(from: data)
			// 2-1
			await updateDatabase(with: articles, for: feed)
			return articles
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Await &amp;amp; Continuation&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 어떻게 Concurrency에서는 CPU 코어 수 만큼의 스레드만 존재하며, Thread Context Switch가 없는 모델을 사용할 수 있는걸까? 간단한 예시로 Database에 write 하는 로직은 대충 아래와 같다고 가정하고, 실제로 어떻게 동작하는지 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1771900086085&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// await updateDatabase(with: articles, for: feed)

func updateDatabase(with article: Article, for feed: Feed) async {
	await feed.add(articles)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기(async) 함수를 호출하기위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;await&lt;/b&gt;을 만나면, 스레드는 block되지 않고, 작업을&amp;nbsp;&lt;b&gt;suspend&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;이 때, await 지점 이후의 코드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;heap&lt;/b&gt;에 스택구조(Last-In-First-Out)로 저장된다.&lt;/li&gt;
&lt;li&gt;그리고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;스레드&lt;/b&gt;는 suspend된 작업을 내버려두고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;다른 작업을 진행&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;다른 작업을 진행하고 있던 스레드가 해제되고, suspend되었던 작업도 재게될 수 있게되면,&lt;br /&gt;heap에 저장해두었던&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;continuation&lt;/b&gt;을 다시 가져와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;resume&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;➡️ 따라서 Swift Concurrency의 async-await 구조는 스레드를 block하지 않고 비동기 작업을 수행할 수 있다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Tracking Task Dependency&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;: Swift 런타임이 코드 내의 task들 사이에 존재하는 실행 순서와 의존 관계를 명확하게 파악하고 관리하는 기능이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 아래와 같은 케이스에서 Dependency를 확인할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 설명했듯이 await 키워드를 만나면, 그 아래 로직(= 코드)은 continuation이 되고,&lt;br /&gt;이 continuation은 await된 async 함수가 완료(= resume)되어야만 실행될 수 있다.&lt;/li&gt;
&lt;li&gt;부모 task가 여러 자식 task를 생성할 경우, 런타임은 부모 task가 진행되기 위해 자식 task들이 모두 완료되어야 한다는 계층적 의존성을 명확히 인식한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, Swift에서 Task들은 런타임에 등록된 Task(= continuation / child tasks)들만 await할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동일한 스레드 내에서 위 케이스 외의 Task를 수행하는것을 방지하는 효과가 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 Tracking 기능을 통해 Swift는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Forward progress of threads를 보장&lt;/b&gt;하는 '런타임 계약(Runtime contract)'을 유지할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Cooperative thread pool&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Swift Concurrency의 Runtime contract를 지원하기 위해&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 개념인 Cooperative thread pool이 도입되었고, 이는 아래를 보장한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 코드(예: async 함수, Task, Actor 등)가 실행될 때, 개발자가 별도로 특정 실행 환경(예: MainActor)을 지정하지 않으면 기본적으로 이 Cooperative thread pool 위에서 실행된다.&lt;/li&gt;
&lt;li&gt;GCD와 달리 최대 CPU 코어 갯수만큼만 스레드를 생성할 수 있다.&lt;/li&gt;
&lt;li&gt;Worker thread는 block되지 않는다.&lt;/li&gt;
&lt;li&gt;Thread explosion 및 과도한 Context switching이 발생하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Concurrency 도입시 주의사항&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자, 이제 어떻게 Concurrency가 동작하는지 알아봤으니 코드에 적용해보자. 단, 아래 주의사항을 유념해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Performance&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동시성(= Concurrency)에는 비용이 따른다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운&amp;nbsp;Task를&amp;nbsp;생성하고&amp;nbsp;관리하는&amp;nbsp;데는&amp;nbsp;추가적인&amp;nbsp;메모리&amp;nbsp;할당과&amp;nbsp;런타임&amp;nbsp;로직이&amp;nbsp;필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Task를 생성하고 관리하는 비용보다 이점이 클 때 도입한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;예를 들어,&lt;br /&gt;단순히 UserDefaults에서 정수 값을 하나 읽어오는 것과 같이 아주 사소한 작업을 위해 별도의 Child Task를 만드는 것은 비효율적이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Concurrency를 도입했을 때, 실제 성능이 개선되는지 Instruments system trace를 통해 측정하고 검증한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;await 전후의 원자성(= atomicity)&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;await 키워드는 atomicity를 파괴하는 키워드이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;await 이전 코드를 실행한 스레드와, await 이후의 코드(Continuation)를 실행하는 스레드가 다를 수 있다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;4&quot;&gt;await를 사이에 두고 락(Lock)을 걸면 안된다.&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;5&quot;&gt;특정 스레드에 저장해 둔 데이터가 유지된다고 보장할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Runtime Contract&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드 작성시, Runtime Contract(= Forward progress of threads)를 해치지 않아야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러기 위해선 아래 방법들을 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;await,&amp;nbsp;&lt;b&gt;Actor&lt;/b&gt;,&amp;nbsp;&lt;b&gt;Task Group&lt;/b&gt;&amp;nbsp;사용하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;(안전)&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Swift의 Cooperative thread pool 환경에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;스레드가 멈추지 않고 계속 작업을 수행할 수 있다는 '런타임 계약(Runtime contract)'을 위반하지 않는 동시성 제어 도구&lt;/b&gt;들이다.&lt;/li&gt;
&lt;li&gt;의존성이 컴파일 타임에 명확히 파악되기 때문에, Swift 컴파일러가 Runtime Contract가 유지되도록 강제한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;os_unfair_lock, NSLock 사용하기&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;(주의 필요)&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; data-start-index=&quot;494&quot;&gt;동기 코드 내에서 데이터 동기화를 위해 사용된다면,&amp;nbsp;&lt;b&gt;안전&lt;/b&gt;하다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; data-start-index=&quot;588&quot;&gt;경합 상황에서 스레드를 잠깐 차단(Block)할 수는 있지만, 락(Lock)을 획득한 스레드가 락을 해제하기 위해 항상 앞으로 작업을 진행할 수 있으므로 전진 진행 계약을 위반하지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; data-start-index=&quot;710&quot;&gt;이 락들은 컴파일러가 올바른 사용을 도와주지 않으므로 주의해서 사용해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;DispatchSemaphores, pthread_cond, NSCondition, pthread_rw_lock, etc..&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;(안전하지 않음)&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Swift 런타임으로부터 의존성 정보를 숨긴 채로 코드 실행 흐름에 의존성을 만들어내며, 결과적으로 스레드를 다른 스레드가 깨워줄 때까지 무기한 대기(Block)시켜 Runtime Contract를 위반할 위험이 크다.&lt;/li&gt;
&lt;li&gt;아래 예시처럼 Task 범위를 넘어서 해당 도구들을 사용하지 않아야 한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;LIBDISOATCH_COOPERATIVE_POOL_STRICT 환경변수를 1 로 설정하면,&lt;br /&gt;잘못 설정된 blocking 이 있는지 확인 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1771900086088&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func updateDatabase(_ asyncUpdateDatabase: @Sendable @escaping () async -&amp;gt; Void) {
	let semaphore = DispatchSemaphore(value: 0)
    
    Task {
    	await asyncUpdateatabase()
        semaphore.signal()
    }
    
    semaphore.wait()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot; data-heading=&quot;Actor&quot;&gt;동기화 (Synchronization)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Concurrent 환경에서의 동기화는 어떻게 구현할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Swift의&lt;/span&gt;&lt;span style=&quot;color: #0e0e0e; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;Actor&lt;/b&gt;는 아래의 특성들을 가지며, Concurrent 환경에서의 동기화를 구현할 때 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 상호 배제 (Mutual exclusion)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Actor는 한 번에 최대 하나의 메서드 호출만 실행되도록 보장하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;완벽한 상호 배제를 제공&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;Actor의 Mutual State에 여러 스레드가 동시에 접근할 수 없게 되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;데이터 경합(Data race)을 원천적으로 방지&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  그렇다면 아래와 같이 다른 형태의 Mutual exclusion과는 뭐가 다를까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 Serial Queue를 예시로 들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1771900086089&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;databaseQueue.sync { updateDatabase(articles, for: feed) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;경합(contention)이 없는 경우(= 큐가 돌고 있지 않음)&lt;/b&gt;라면,&lt;br /&gt;호출된 스레드는 큐에 등록된 작업을 실행하기 위해 context switch 없이 재사용된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경합&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;이 있는 경우(= 큐가 이미 사용되고 있음)&lt;/b&gt;라면,&lt;br /&gt;&lt;/span&gt;호출된 스레드는 block된다. 이러한 block은 Thread Explosion을 발생시키는 원인이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;이는 Lock을 사용할 때에도 동일하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;그렇다면 아래처럼 async하게 호출한다면 어떨까?&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771900086089&quot; class=&quot;css&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;databaseQueue.async { /* background work */ }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;경합이 있는 경우&lt;/b&gt;에도 block되지 않는다. 따라서 Thread Explosion을 발생시키지 않는다.&lt;/li&gt;
&lt;li&gt;하지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;b&gt;경합이 없는 경우&lt;/b&gt;에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;async 블록내의 코드를 실행할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;새로운 스레드를 생성시킨다. (&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;호출된 스레드는 다른 작업을 진행함)&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;따라서 async를 많이 사용하게 되면, 스레드를 과하게 생성하게 되고 context switch도 빈번하게 일어난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;  이러한 단점들 때문에 Actor를 사용하기를 권장한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Actor는&amp;nbsp;&lt;/span&gt;Cooperative Pool에 의해 효과적으로 관리&lt;/b&gt;되기 때문에 아래의 이점을 갖는다.&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;경합이 없는 경우&lt;/b&gt;에는 호출된 스레드를 재사용하고&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;경합이 있는 경우&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;에도&amp;nbsp;&lt;/span&gt;&lt;span&gt;호출된 스레드는 해당 작업을&amp;nbsp;&lt;/span&gt;suspend할 뿐 스레드가&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;block되지 않는다.&lt;br /&gt;&lt;/span&gt;(= 호출된 스레드는 다른 작업을 하게 된다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;  그렇다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Actor Hopping&lt;/b&gt;이 발생할 때에는 어떻게 동작할까?&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(Actor Hopping = 코드의 실행 흐름이 한 Actor에서 다른 Actor로 전환되는 과정)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;&lt;b&gt;경합이 없는 경우&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;A액터에서 B액터로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;직접 호핑&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;이 과정에서 A액터를 진행하던 스레드가 유지되며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;스레드가 block되지 않는다&lt;/b&gt;.&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;런타임은 A액터의 작업중이던 항목을 suspend하고, B액터에 대한 새 작업 항목을 생성한다.&lt;br /&gt;(이때, suspended actors는 별도로 관리된다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;3&quot;&gt;&lt;b&gt;경합이 있는 경우&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;3&quot;&gt;A액터가 이미 작업을 처리 중일 때 B액터로 부터 A액터의 작업에 접근하면,&lt;br /&gt;A액터는 이미 활성 작업 항목이 있으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;새로운 작업 항목을 생성한다.&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;3&quot;&gt;하지만 Mutual exclusion의 특성으로 새로 생성된 작업 항목은 바로 실행되지 않고,&amp;nbsp; 보류한다.&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;3&quot;&gt;이 과정에서 B액터에서 작업중이던 항목은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;suspend&lt;/b&gt;되며, B액터를 호출하던 스레드는 해제되어 다른 작업(ex: C액터의 작업 항목)을 진행한다.&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;4&quot;&gt;이후, A액터에서 작업중이던 항목이 완료되면 런타임은 보류된 A액터의 작업 항목을 실행하거나 suspend된 다른 액터의 작업 항목을 resume하거나 아예 다른 작업을 수행할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 재진입성과 우선순위 지정 (Reentrancy and prioritization)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;특히나 경합이 많은 경우, 시스템은 우선순위를 따져 무엇을 먼저 실행할지 결정해야한다. (&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;trade-offs 발생)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통은 유저 인터랙션에 의한 작업인 경우가 백그라운드에서 진행되어야하는 작업보다 높은 우선순위를 가질 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 우선순위는 어떻게 정해지는 걸까?&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #0e0e0e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단, 기존 GCD의 경우 어떻게 동작하는지 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1771900086090&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// User Initiated (우선순위 ⬆️)
databaseQueue.async { fetchLatestForDisplay() }

// Background (우선순위 ⬇️)
databaseQueue.async { backUpToiCloud() }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD의 Serial Queue는 엄격한 선입선출(FIFO) 모델을 따랐기 때문에, 중요도가 낮은 작업들이 먼저 큐에 쌓여 있으면,&lt;br /&gt;UI 업데이트 같은 높은 우선순위의 작업이 끝없이 대기해야 하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;우선순위 역전(Priority inversion) 문제&lt;/b&gt;가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;직렬 큐는 큐의 모든 작업의 우선순위를 높여 이를 해결하려고 하지만, 이는&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;핵심 문제를 해결하지 못했다&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;  이러한 문제가 있어 Actor를 사용하기를 권장한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액터는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;재진입성(reentrancy)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;개념 덕분에 시스템이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;작업의 우선순위를 잘 지정&lt;/b&gt;할 수 있도록 설계되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(액터의 재진입성 = 액터가 일시 중단된 동안 새로운 작업 항목이 진행될 수 있음을 의미)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771900086091&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// User Initiated (우선순위 ⬆️)
await databaseQueue.fetchLatestForDisplay()

// Background (우선순위 ⬇️)
await databaseQueue.backUpToiCloud()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 액터는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;엄격한 선입선출(FIFO) 모델을 따르지 않고도 작업 항목을 실행할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 런타임은&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;높은 우선순위 작업을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;저우선순위 작업보다 먼저 실행되도록 선택할 수 있다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 우선순위 역전 문제를 직접 해결하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;더 효과적인 스케줄링 및 리소스 활용&lt;/b&gt;을 가능하게 한다. (= Actor reprioritization)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 메인 액터 (Main actor)&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 액터는 시스템의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;메인 스레드&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;개념을 추상화하므로 특성이 다소 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인터페이스를 업데이트할 때 반드시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;MainActor와 호출을 주고받아야&lt;/b&gt;&lt;span&gt;&amp;nbsp;한&lt;/span&gt;다.&lt;br /&gt;메인 스레드는 Cooperative Pool의 스레드와 분리되어 있으므로&lt;span&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Context Switch&lt;/b&gt;가&lt;/span&gt;&amp;nbsp;필요합니다.&lt;br /&gt;(= 메인 액터를 실행하는 스레드는 C&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;ooperative Pool에 의해 관리되지 않는다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771900086091&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@MainActor func updateArticles(for ids: [ID]) async throws {
    for id in ids {
        // Context Switch 발생!! (Main Actor &amp;rarr; Custom Actor)
    	let article = try await database.loadArticle(with: id) // Custom Actor
        // Context Switch 발생!! (Custom Actor &amp;rarr; Main Actor)
        await updateUI(for: article) // Main Actor
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 반복문 내에서 메인 액터와 그 외 액터 간에 자주 호핑하는 코드는&lt;span&gt;&lt;br /&gt;&lt;/span&gt;과도한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Context Switch&lt;/b&gt;는 발생시키고,&amp;nbsp;&lt;b&gt;overhead&lt;/b&gt;를 야기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션이 컨텍스트 스위치에 많은 시간을 소비한다면,&lt;br /&gt;&lt;span&gt;아래와 같이&amp;nbsp;&lt;/span&gt;&lt;b&gt;배치(batch) 처리&lt;/b&gt;하도록 코드를 재구성해&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Context Switch 수를 줄여야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771900086092&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// for문 제거됨
@MainActor func updateArticles(for ids: [ID]) async throws {
    // Context Switch 발생!! (Main Actor &amp;rarr; Custom Actor)
    let articles = try await database.loadArticle(with: ids) // Custom Actor
    // Context Switch 발생!! (Custom Actor &amp;rarr; Main Actor)
    await updateUI(for: articles) // Main Actor
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cooperative Pool의 액터 간 호핑은 빠르지만,&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;MainActor&lt;/span&gt;와의 호핑은 비용이 많이 들어 주의&lt;/b&gt;해야 한다는 사실을 기억하자!&lt;/p&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유익했다면 댓글/공감 남겨주세요~~ 작성자에게 큰 힘이 됩니다 ☺️&lt;/p&gt;</description>
      <category>Swift</category>
      <category>@MainActor</category>
      <category>actor</category>
      <category>ios</category>
      <category>SWiFT</category>
      <category>swift concurrency</category>
      <category>WWDC21</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/45</guid>
      <comments>https://red-cherry-ring.tistory.com/45#entry45comment</comments>
      <pubDate>Tue, 24 Feb 2026 11:28:32 +0900</pubDate>
    </item>
    <item>
      <title>Swift Concurrency #4 - Actor로 Mutable State 보호하기</title>
      <link>https://red-cherry-ring.tistory.com/44</link>
      <description>&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=9Nqox5SeYEM&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WWDC21:&amp;nbsp;Protect&amp;nbsp;mutable&amp;nbsp;state&amp;nbsp;with&amp;nbsp;Swift&amp;nbsp;actors&amp;nbsp;|&amp;nbsp;Apple&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=9Nqox5SeYEM&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/CryXA/dJMb81GUG9u/v1kvJ9pMPtCi96K7CcYnak/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bVT4Ih/dJMb9lL82rE/NoGKdIk7gMwRkkQHQeKs10/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;WWDC21: Protect mutable state with Swift actors | Apple&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/9Nqox5SeYEM&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동시성 프로그래밍의 어려움&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Data Race란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개 이상의 스레드에서 동시에 같은 데이터에 접근할 때, 최소 하나 이상의 쓰기(Write) 작업이 발생하면 생기는 동시성 문제.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data Race는 타이밍에 따라 결과가 달라지는 특성이 있어 디버깅이 매우 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 예를 들면 아래와 같고, 상황에 따라 각 print문에서 1/2 혹은 1/1 혹은 2/2 가 프린트 될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1771826393008&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Counter {
	var value = 0
	
	func increment() -&amp;gt; Int {
		value = value + 1 // read &amp;amp; write
		return value
	}
}

let counter = Counter()

Task.detached {
	print(counter.increment()) //  ️
}

Task.detached {
	print(counter.increment()) //  ️
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Value Semantics를 통한 Data Race 예방&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Value Semantics란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에서 사용하는 메모리 효율을 높이기 위한 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 객체에 대한 참조 사용을 줄이고 값 중심으로 데이터를 다룸으로써, 불필요한 메모리 사용을 최소화하려는 프로그래밍 패턴을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data Race는 '공유되는 가변 상태' 때문에 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;Value Semantics&lt;/b&gt;를 이용하면, data race를 방지할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Value Type 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 피하는 첫 번째 방법은 struct와 같은 값 타입(Value type)을 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 타입은 변형이 지역적(Local)으로 일어나며, 불변(let) 프로퍼티는 완벽히 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 봤던 class Counter 코드 예시를 struct Counter로 변경해보면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1771829532959&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Counter {
	var value = 0
	
	mutating func increment() -&amp;gt; Int {
		value = value + 1 // read &amp;amp; write
		return value
	}
}

var counter = Counter()

Task.detached {
	var counter = counter // (new copy)
	print(counter.increment()) //  ️
}

Task.detached {
	var counter = counter // (new copy)
	print(counter.increment()) //  ️
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;항상 1/1 이 프린트 되며, data race를 방지할 수 있다.&lt;br /&gt;  하지만 우리가 원하는 건 'value' 프로퍼티의 값을 공유하면서 data race를 방지하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(프로퍼티 값을 공유하면서 data race를 방지하는 방법은 Actor을 설명하면서 이어하겠다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 컬렉션의 COW 매커니즘 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 배열(Array), 딕셔너리(Dictionary)와 같은 컬렉션 타입은 &lt;b&gt;COW(Copy-on-Write)&lt;/b&gt; 메커니즘을 사용하므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 할당할 때가 아니라 실제 변경(write)이 일어날 때만 복사되어 안전하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 예로 들면,&lt;/p&gt;
&lt;pre id=&quot;code_1771828865140&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var array1 = [1, 2]
var array2 = array1 // array1 과 array2 는 같은 버퍼를 공유 (copy X)

array1.append(3) // (new copy)
array2.append(4) // (original)

print(array1) // [1, 2, 3]
print(array2) // [1, 2, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;var array2 = array1&lt;/span&gt;이 호출될 때에는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;array1 배열이 array2 배열로 copy되는 것이 아닌, array1 과 array2 는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;같은 버퍼를 공유&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 참조 카운트(reference count)값은 2이 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 &lt;span style=&quot;background-color: #99cefa;&quot;&gt;array1.append(3)&lt;/span&gt;이 호출될 때에는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;값이 변경되므로 기존 버퍼와 동일한 값을 갖도록 copy되고, copy된 버퍼에 '3'이 append된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;array2.append(4)&lt;/span&gt;이 호출될 때에는 copy가 필요하지 않으므로, 기존 버퍼를 그대로 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Shared Mutable State와 기존 동기화 방식의 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 값 타입만 사용할 수는 없으며, 때로는 여러 스레드에서 동시 작업에서 데이터를 공유하고 수정할 수 있는 상태일 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 기존에는 &lt;b&gt;Atomics, Locks, Serial dispatch queues&lt;/b&gt; 등의 동기화 도구를 사용했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&amp;nbsp;이러한 도구들은 완벽하게 이해하고 매번 정확하게 사용해야만 Data Race를 막을 수 있다는 치명적인 단점이 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Actor의 등장과 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 Swift에 기본 내장된 동기화 메커니즘이 바로 &lt;b&gt;Actor&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;독립적인 상태(Actor Isolation):&lt;/b&gt; Actor는 고유한 state를 가지며, 이 state는 외부로부터 철저히 격리된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상호 배제(Mutual Exclusion):&lt;/b&gt; Actor 내의 state에는 한 번에 오직 하나의 작업만 접근할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참조 타입(Reference Type):&lt;/b&gt; class처럼 참조 타입이며, 프로퍼티, 함수, 상속 모두 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1771830391707&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actor Counter { // actor 로만 변경하면, data race 가 보장된다.
	var value = 0
	
	func increment() -&amp;gt; Int {
		value = value + 1
		return value
	}
}

let counter = Counter()

Task.detached {
	print(await counter.increment()) //   await
}

Task.detached {
	print(await counter.increment()) //   await
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 코드를 actor 변경하면, 상황에 따라 1/2 혹은 2/1이 프린트 되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 Actor의 특성으로 data race 방지가 되는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Actor 외부에 있는 코드가 Actor 내부 state에 접근하려면 반드시 await 키워드를 사용하여 비동기적으로 상호작용해야 한다.&lt;/li&gt;
&lt;li&gt;만약 Actor가 이미 다른 작업을 처리하느라 바쁘다면, 새로 요청된 코드는 &lt;b&gt;일시 중단(Suspend)&lt;/b&gt; 되고 해당 스레드(CPU)는 다른 작업을 수행한다.&lt;/li&gt;
&lt;li&gt;이후 Actor가 free되면, suspend 되었던 코드를 다시 &lt;b&gt;재개(Resume)&lt;/b&gt; 하여 작업을 마저 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Actor Reentrancy(재진입성) 주의사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 단, Actor 내에서 await을 사용할 때에는 주의가 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1771831016192&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actor ImageDownloader {
    private var cache: [URL: Image] = [:]

    func image(from url: URL) async throws -&amp;gt; Image? {
        if let cached = cache[url] {
            return cached
        }

        let image = try await downloadImage(from: url) // reentrancy 발생
        
        cache[url] = image
        return image
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 예시에서 동일한 URL로 image(from:) 함수가 동시에 2번 호출되었고, 두 호출 모두 await에서 suspend된 상태라고 가정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 각각의 호출이 resume되는 시점에 cache가 중복 업데이트되거나 덮어씌워지는 버그가 발생할 수 있다.&lt;br /&gt;data race는 없지만 캐싱기능을 제대로 수행하지 못하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;await 이후에, 중단되었던 시간 동안 상태가 변경되지 않았는지 (예: 캐시에 이미 값이 있는지) &lt;b&gt;다시 확인&lt;/b&gt;해야 하거나&lt;/li&gt;
&lt;li&gt;await 호출 전에 작업이 현재 진행 중(Pending / In Progress)임을 Dictionary 등에 저장해두어, 중복 실행을 차단해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Actor와 nonisolated 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에서는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Actor 내부에 있지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;실제로는 Actor 외부에 있는 것처럼 동작&lt;/b&gt;하도록 &lt;b&gt;nonisolated&lt;/b&gt; 키워드를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nonisolated가 적용된 함수는 Actor 외부에서 await 없이 호출할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, Actor isolation에서 벗어났으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor의 mutable state(= var로 선언된 프로퍼티)에는 접근할 수 없고, 오직 &lt;b&gt;불변 상태(let)에만 접근 가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nonisolated 함수는 Actor에서 Equatable, Hashable 등의 프로토콜을 채택하고자 할 때, 동기적(Synchronous)으로 호출되어야 하는 delegate 함수들을 구현할 때 자주 사용되며, 코드 예시는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1771834164266&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
}

extension LibraryAccount: Hashable {
    nonisolated func hash(into hasher: inout Hasher) { //   nonisolated 키워드 사용
        hasher.combine(idNumber)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Actor와 Closure&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor 내에서 동기적으로 호출되는 클로저(예: reduce)는 Actor-isolated하므로 await가 필요 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Task.detached 내부의 클로저처럼 동시적으로 실행되는 클로저는 더 이상 Actor-isolated하지 않으므로,&lt;br /&gt;Actor 메서드를 호출할 때 await 키워드가 반드시 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771834344259&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension LibraryAccount {
    func readSome(_ book: Book) -&amp;gt; Int { ... }

    func read() -&amp;gt; Int {
        booksOnLoan.reduce(0) { book in // Actor-isolated O
            readSome(book) // await 필요하지 않음
        }
    }

    func readLater() {
        Task.detached { // Actor-isolated X
            await self.read() // await 필요
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면... 아래 코드 예시와 같이 다른 Actor 사이에서 Reference Type 데이터를 주고 받으면 어떻게 될까?&lt;/p&gt;
&lt;pre id=&quot;code_1771835710634&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
    func selectRandomBook() -&amp;gt; Book? { &amp;hellip; }
}

class Book {
    var title: String
    var authors: [Author]
}

// -------

// actor 외부에서 구현된 함수
func visit(_ account: LibraryAccount) async {
    guard var book = await account.selectRandomBook() else {
        return //   data race 발생 위험!
    }
    book.title = &quot;\(book.title)!!!&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;selectRandomBook()가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;actor 경계를 넘어 Book 인스턴스를 외부로 전달&lt;/b&gt;하고 있고,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Book은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;mutable한 참조 타입(class)&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이라서&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;data race 위험&lt;/b&gt;이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세히 설명하면 아래 코드가 호출 될 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LibraryAccount는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;actor&lt;/b&gt; 이고, visit 함수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;actor 외부&lt;/b&gt;에서 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게다가 await는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;actor의 격리 영역(isolation)을 벗어난다는 신호&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;guard var book = await account.selectRandomBook() else { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래와 같이 &lt;b&gt;동시에 다른 Task에서 접근하면?&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;// actor 내부
account.booksOnLoan[0].title = &quot;Swift&quot;

// actor 외부
book.title = &quot;Swift!!!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;data race가 발생할 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Swift에는 이러한 상황을 &lt;b&gt;컴파일 단계에서 차단&lt;/b&gt;하고자 Sendable 이라는 프로토콜이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sendable&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sendable&lt;/b&gt;은 동시성(Concurrency) 환경에서 &lt;b&gt;서로 다른 액터(Actor) 간에 값을 안전하게 공유할 수 있는 타입&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 코드나 액터 간에 데이터를 주고받을 때는 기본적으로 Sendable 타입을 사용해야 하며, 이를 통해 컴파일러 단계에서 데이터 경합(Data race)을 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 아래와 같은 조건이 만족하는 경우에만 Sendable 프로토콜을 만족할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;값 타입 (Value Types):&lt;/b&gt; 구조체(struct)와 같은 값 타입은 복사할 때마다 독립적인 값을 가지므로 기본적으로 안전하여 Sendable이 될 수 있습니다. 단, 구조체 내부의 &lt;b&gt;모든 저장 프로퍼티가 Sendable 타입&lt;/b&gt;이어야만 구조체 전체가 Sendable로 인정됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;액터 (Actor Types):&lt;/b&gt; 액터는 내부적으로 가변 상태에 대한 접근을 자체적으로 동기화(상호 배제)하므로 그 자체로 Sendable입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제네릭 및 컬렉션 타입:&lt;/b&gt; 배열(Array)과 같은 제네릭 타입은 &lt;b&gt;내부에 담긴 요소(Generic arguments)가 Sendable일 때만 조건부로 Sendable&lt;/b&gt;이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스는 참조 타입(Reference Type)이며 가변 상태를 가지기 때문에 &lt;b&gt;대부분의 클래스는 Sendable이 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 클래스를 여러 액터 간에 공유하면, 각 액터가 동일한 가변 상태에 동시 접근하여 데이터 경합이 발생할 위험이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 클래스가 Sendable이 될 수 있는 예외적인 경우는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스와 그 하위 클래스가 오직 불변 데이터(Immutable data)(= let 프로퍼티)만 가지고 있거나&lt;/li&gt;
&lt;li&gt;락(Lock) 등을 사용하여 내부적으로 동기화 처리를 완벽하게 해둔 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sendable 사용 예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771899504546&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Pair&amp;lt;T, U&amp;gt; {
	var first: T
	var second: U
}

// 이렇게 확장해서 사용할 수도 있다!
extension Pair: Sendable where T: Sendable, U: Sendable {
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;@Sendable 클로저&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수나 클로저 역시 다른 액터로 전달될 수 있으므로 Sendable 개념이 적용되며, 이 경우 @Sendable 어트리뷰트를 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Sendable 함수 예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771899554464&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let work: @Sendable () -&amp;gt; Void = { // Sendable 프로토콜을 채택한다는 뜻
    print(&quot;Safe closure&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, data race를 막기 위해 @Sendable 클로저는 다음의 엄격한 제약 조건을 따른다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클로저 외부의 &lt;b&gt;가변 지역 변수(Mutable local variable)를 캡처할 수 없다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;클로저가 &lt;b&gt;캡처하는 모든 값은 반드시 Sendable 타입&lt;/b&gt;이어야 한다.&lt;/li&gt;
&lt;li&gt;동기적으로 동작하는 Sendable 클로저는 특정 액터에 격리(Actor-isolated)될 수 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1771899622252&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1
var count = 0
let task: @Sendable () -&amp;gt; Void = {
    count += 1  // ❌ mutable capture
}

// 2
let number: Int = 42
let task: @Sendable () -&amp;gt; Void = {
    print(number) // ✅ immutable capture
}

// 3
static func detached(operation: @Sendable () async -&amp;gt; Success) -&amp;gt; Task&amp;lt;Success, Never&amp;gt;

actor Counter {
    var value = 0
    
    func increment() {
        Task.detached {
            self.value += 1   // ❌ await 을 통해서 접근해야 한다.
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Main Actor (@MainActor)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main Actor는 &lt;b&gt;메인 스레드(Main thread)를 추상화한 특수한 Actor&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 업데이트와 같이 반드시 메인 스레드에서 수행되어야 하는 작업들을 안전하게 처리하기 위해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스나 뷰 컨트롤러, 특정 함수에 @MainActor를 붙이면 해당 코드가 항상 메인 스레드에서 실행된다.&lt;/p&gt;
&lt;pre id=&quot;code_1771831592924&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MainActor
func checkedOut(_ booksOnLoan: [Book]) {
    booksView.checkedOutBooks = booksOnLoan // UI Updates
}

// 다른 actor 와 마찬가지로 actor 외부에서 접근시 await 필요한다
await checkedOut(booksOnLoan)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Actor와 마찬가지로, 메인 액터 내부에서도 메인 스레드 실행이 필요 없는 무거운 작업 등은 nonisolated로 표기하여 격리에서 제외할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1771831682365&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MainActor 
class MyViewController: UIViewController {
    func onPress(&amp;hellip;) { &amp;hellip; } // implicitly @MainActor

    nonisolated func fetchLatestAndDisplay() async { &amp;hellip; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-start=&quot;4845&quot; data-end=&quot;4848&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유익했다면 댓글/공감 남겨주세요~~ 작성자에게 큰 힘이 됩니다 ☺️&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Swift</category>
      <category>actor</category>
      <category>copy on write</category>
      <category>data race</category>
      <category>ios</category>
      <category>nonisolated</category>
      <category>sendable</category>
      <category>swift concurrency</category>
      <category>WWDC21</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/44</guid>
      <comments>https://red-cherry-ring.tistory.com/44#entry44comment</comments>
      <pubDate>Tue, 24 Feb 2026 11:25:17 +0900</pubDate>
    </item>
    <item>
      <title>Swift Concurrency #3 - Task cancellation과 hierarchy 심화</title>
      <link>https://red-cherry-ring.tistory.com/41</link>
      <description>&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Ce0GsaRCMew&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WWDC23:&amp;nbsp;Beyond&amp;nbsp;the&amp;nbsp;basics&amp;nbsp;of&amp;nbsp;structured&amp;nbsp;concurrency&amp;nbsp;|&amp;nbsp;Apple&lt;/a&gt;&lt;span style=&quot;color: #777777; text-align: center;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Ce0GsaRCMew&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/c07Kmf/dJMb87f3U1v/JWEFFpVXFMKsB2rNpkG5y0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=1048_284_1094_334,https://scrap.kakaocdn.net/dn/REDJy/dJMb85WQLG8/8ak0NkfQJSr9UQTbtUITQ0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=1048_284_1094_334,https://scrap.kakaocdn.net/dn/7P65B/dJMb9aKCBhC/Bebkt7kmFAdlxgSgbtyR1k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=1048_284_1094_334&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;WWDC23: Beyond the basics of structured concurrency | Apple&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Ce0GsaRCMew&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 data-end=&quot;450&quot; data-start=&quot;442&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;p data-end=&quot;531&quot; data-start=&quot;452&quot; data-ke-size=&quot;size16&quot;&gt;Swift Concurrency를 공부하면서 Task.cancel()을 호출하면 모든 작업이 즉시 멈춘다고 막연히 생각하고 있었다.&lt;/p&gt;
&lt;p data-end=&quot;646&quot; data-start=&quot;533&quot; data-ke-size=&quot;size16&quot;&gt;그런데 WWDC 세션 &lt;b&gt;&amp;ldquo;Beyond the basics of structured concurrency&amp;rdquo;&lt;/b&gt;를 보면서 내가 이해하고 있던 cancellation 모델이 꽤 단순했다는 걸 알게 됐다.&lt;/p&gt;
&lt;p data-end=&quot;646&quot; data-start=&quot;533&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;802&quot; data-start=&quot;648&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;Task cancellation이 실제로 어떻게 전파되고, 왜 &amp;lsquo;cooperative&amp;rsquo;하다고 말하는지&lt;/b&gt;, 그리고 그로 인해 등장한 &lt;b&gt;withTaskCancellationHandler&lt;/b&gt;까지 헷갈렸던 포인트 위주로 정리해보려고 한다.&lt;/p&gt;
&lt;hr data-end=&quot;807&quot; data-start=&quot;804&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;844&quot; data-start=&quot;809&quot; data-ke-size=&quot;size23&quot;&gt;Task hierarchy와 cancellation 전파&lt;/h3&gt;
&lt;p data-end=&quot;908&quot; data-start=&quot;846&quot; data-ke-size=&quot;size16&quot;&gt;Structured concurrency에서 Task는 &lt;b&gt;트리 구조(task hierarchy)&lt;/b&gt;를 가진다.&lt;/p&gt;
&lt;pre id=&quot;code_1768371319062&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func makeSoup(order: Order) async throws -&amp;gt; Soup {
    async let pot = stove.boilBroth()
    async let choppedIngredients = chopIngredients(order.ingredients)
    async let meat = marinate(meat: .chicken)

    let soup = try await Soup(meat: meat, ingredients: choppedIngredients)
    return try await stove.cook(
        pot: pot,
        soup: soup,
        duration: .minutes(10)
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1384&quot; data-start=&quot;1315&quot; data-ke-size=&quot;size16&quot;&gt;makeSoup가 부모 Task라면, async let으로 생성된 작업들은 모두 &lt;b&gt;child task&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;p data-end=&quot;1397&quot; data-start=&quot;1386&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1397&quot; data-start=&quot;1386&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은,&lt;/p&gt;
&lt;blockquote data-end=&quot;1465&quot; data-start=&quot;1399&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1465&quot; data-start=&quot;1401&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부모 Task가 cancel되면, 그 아래의 모든 child task에도 cancellation이 전파된다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1510&quot; data-start=&quot;1467&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1510&quot; data-start=&quot;1467&quot; data-ke-size=&quot;size16&quot;&gt;다만, &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Task cancellation는 협동적이기(cooperative) 때문에&lt;br /&gt;&lt;/span&gt;이 cancellation은 &lt;b&gt;즉시 실행 중단&lt;/b&gt;을 의미하지 않는다.&lt;/p&gt;
&lt;hr data-end=&quot;1515&quot; data-start=&quot;1512&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1556&quot; data-start=&quot;1517&quot; data-ke-size=&quot;size23&quot;&gt;Task cancellation은 왜 cooperative일까?&lt;/h3&gt;
&lt;p data-end=&quot;1615&quot; data-start=&quot;1558&quot; data-ke-size=&quot;size16&quot;&gt;Task cancellation은 &lt;b&gt;cooperative&lt;/b&gt;하다.&lt;br /&gt;즉, &amp;ldquo;강제로 멈춘다&amp;rdquo;가 아니라&lt;/p&gt;
&lt;blockquote data-end=&quot;1650&quot; data-start=&quot;1617&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1650&quot; data-start=&quot;1619&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;취소되었다는 표시(cancel flag)를 남긴다&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1658&quot; data-start=&quot;1652&quot; data-ke-size=&quot;size16&quot;&gt;에 가깝다.&lt;/p&gt;
&lt;pre id=&quot;code_1768371362161&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func makeSoup(order: Order) async throws -&amp;gt; Soup {
	async let pot = stove.boilBroth()
	
	guard !Task.isCancelled else {
		throw SoupCancellationError()
	}
	
	async let choppedIngredients = chopIngredients(order.ingredients)
	async let meat = marinate(meat: .chicken)
	let soup = try await Soup(meat: meat, ingredients: choppedIngredients)
	return try await stove.cook(pot: pot, soup: soup, duration: .minutes(10))
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cancel flag가 이 guard 이전에 설정되면 &amp;rarr; throw를 통해 이후 작업은 실행되지 않는다&lt;/li&gt;
&lt;li data-end=&quot;1844&quot; data-start=&quot;1795&quot;&gt;guard를 &lt;b&gt;통과한 뒤에 cancel flag가 설정&lt;/b&gt;되면 &amp;rarr; 아래 비동기 작업이 모두 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1869&quot; data-start=&quot;1846&quot; data-ke-size=&quot;size16&quot;&gt;이 부분을 이해하면서 이런 생각이 들었다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1934&quot; data-start=&quot;1871&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1934&quot; data-start=&quot;1873&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;expensive한 작업마다 isCancelled 나 &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;checkCancellation() &lt;/span&gt;를 계속 체크해야 하나?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;이건 너무 번거롭지 않나?&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1984&quot; data-start=&quot;1936&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1984&quot; data-start=&quot;1936&quot; data-ke-size=&quot;size16&quot;&gt;이 지점에서 등장하는 게 바로 withTaskCancellationHandler다.&lt;/p&gt;
&lt;hr data-end=&quot;1989&quot; data-start=&quot;1986&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;2030&quot; data-start=&quot;1991&quot; data-ke-size=&quot;size23&quot;&gt;withTaskCancellationHandler가 필요한 이유&lt;/h3&gt;
&lt;p data-end=&quot;2072&quot; data-start=&quot;2032&quot; data-ke-size=&quot;size16&quot;&gt;Swift는 cancellation을 감지하는 두 가지 방법을 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1768371423434&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Task.isCancelled // 1
try Task.checkCancellation() // 2&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하지만 위 두 방식은 &lt;/span&gt;&lt;b&gt;polling&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2188&quot; data-start=&quot;2134&quot; data-ke-size=&quot;size16&quot;&gt;직접 체크하지 않으면 아무 일도 일어나지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;2188&quot; data-start=&quot;2134&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2199&quot; data-start=&quot;2190&quot; data-ke-size=&quot;size16&quot;&gt;그래서 나온 게:&lt;/p&gt;
&lt;p data-end=&quot;2199&quot; data-start=&quot;2190&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/withtaskcancellationhandler(operation:oncancel:isolation:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;withTaskCancellationHandler(operation:onCancel:)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2309&quot; data-start=&quot;2264&quot; data-ke-size=&quot;size16&quot;&gt;이 API는 &lt;b&gt;Task가 취소되는 순간 onCancel 클로저를 실행&lt;/b&gt;해준다.&lt;/p&gt;
&lt;p data-end=&quot;2348&quot; data-start=&quot;2311&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 AsyncSequence의 next() 구현을 보면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768371475917&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public func next() async -&amp;gt; Order? {
    return await withTaskCancellationHandler {
        let result = await kitchen.generateOrder()
        guard state.isRunning else { return nil }
        return result
    } onCancel: {
        state.cancel()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2633&quot; data-start=&quot;2620&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 포인트는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2705&quot; data-start=&quot;2635&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2655&quot; data-start=&quot;2635&quot;&gt;Task가 suspend 상태(= cancel flag 는 세팅되었으나 실제로는 비동기작업이 종료되지 않은 상태)여도&lt;/li&gt;
&lt;li data-end=&quot;2676&quot; data-start=&quot;2656&quot;&gt;cancellation이 발생하면, &lt;b&gt;onCancel이 즉시 실행&lt;/b&gt;된다는 점이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2749&quot; data-start=&quot;2707&quot; data-ke-size=&quot;size16&quot;&gt;단순 polling으로는 해결되지 않던 문제를 이 방식으로 처리할 수 있다.&lt;/p&gt;
&lt;hr data-end=&quot;2754&quot; data-start=&quot;2751&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;2802&quot; data-start=&quot;2756&quot; data-ke-size=&quot;size23&quot;&gt;polling vs cancellation handler, 언제 써야 할까?&lt;/h3&gt;
&lt;p data-end=&quot;2822&quot; data-start=&quot;2804&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그냥 withTaskCancellationHandler만 쓰는 게 더 좋은 거 아닌가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2822&quot; data-start=&quot;2804&quot; data-ke-size=&quot;size16&quot;&gt;이 부분에서 나도 가장 헷갈렸다.&lt;/p&gt;
&lt;p data-end=&quot;2822&quot; data-start=&quot;2804&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2893&quot; data-start=&quot;2878&quot; data-ke-size=&quot;size16&quot;&gt;추가로 찾아보면서 이렇게 이해했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3115&quot; data-start=&quot;2895&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2980&quot; data-start=&quot;2895&quot;&gt;&lt;b&gt;polling (isCancelled)&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2980&quot; data-start=&quot;2927&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2980&quot; data-start=&quot;2955&quot;&gt;잘못 사용하면, &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,1,0&quot;&gt;반응성 지연 및 배터리 소모.&lt;br /&gt;&lt;/b&gt;취소 신호를 받기 위해 계속 취소여부를 반복적으로 확인해야한다.&lt;/li&gt;
&lt;li&gt;하지만&lt;br /&gt;대량의 데이터를 가공하거나 반복문을 실행할 때나&lt;br /&gt;비동기 함수 사이사이에 로직이 길게 늘어져 있을 때 중단시키기 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3115&quot; data-start=&quot;2982&quot;&gt;&lt;b&gt;withTaskCancellationHandler&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3115&quot; data-start=&quot;3018&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3044&quot; data-start=&quot;3018&quot;&gt;잘못 사용하면, &lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,2,0&quot;&gt;실행 제어 불가.&lt;br /&gt;&lt;/b&gt;신호는 받았지만 실제 돌고 있는 무거운 코드를 강제로 끌 방법이 없음.&lt;/li&gt;
&lt;li data-end=&quot;3115&quot; data-start=&quot;3076&quot;&gt;따라서&lt;br /&gt;Task를 모르는 외부 라이브러리나 Delegate 기반 API를 중단시켜야 할 때 유용하다.&lt;/li&gt;
&lt;li data-end=&quot;3115&quot; data-start=&quot;3076&quot;&gt;단, onCancel 핸들러는 별도의 컨텍스트에서 실행되므로 Thread-safety를 고려해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;3120&quot; data-start=&quot;3117&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;3148&quot; data-start=&quot;3122&quot; data-ke-size=&quot;size23&quot;&gt;우선순위 전파와 inversion&lt;/h3&gt;
&lt;p data-end=&quot;3205&quot; data-start=&quot;3150&quot; data-ke-size=&quot;size16&quot;&gt;또한 structured concurrency에서 인상 깊었던 부분은 &lt;b&gt;우선순위 전파&lt;/b&gt;였다.&lt;/p&gt;
&lt;blockquote data-end=&quot;3249&quot; data-start=&quot;3207&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;3249&quot; data-start=&quot;3209&quot; data-ke-size=&quot;size16&quot;&gt;child task는 parent task의 priority를 상속한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;3346&quot; data-start=&quot;3251&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3346&quot; data-start=&quot;3251&quot; data-ke-size=&quot;size16&quot;&gt;그리고&lt;br /&gt;&lt;b&gt;높은 우선순위의 작업이 낮은 우선순위 작업을 기다리는 상황&lt;/b&gt;이 생기면,&lt;br /&gt;시스템은 기다리는 child task의 priority를 부모 수준으로 올린다.&lt;/p&gt;
&lt;p data-end=&quot;3360&quot; data-start=&quot;3348&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3360&quot; data-start=&quot;3348&quot; data-ke-size=&quot;size16&quot;&gt;이걸 보면서 든 생각:&lt;/p&gt;
&lt;blockquote data-end=&quot;3412&quot; data-start=&quot;3362&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;3412&quot; data-start=&quot;3364&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이거 GCD 시절부터 priority inversion 막으려고 하던 그거 아닌가?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;3431&quot; data-start=&quot;3414&quot; data-ke-size=&quot;size16&quot;&gt;맞다.&lt;br /&gt;다만 차이는 이거다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3516&quot; data-start=&quot;3433&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3468&quot; data-start=&quot;3433&quot;&gt;이상적인 해결: inversion 자체가 발생하지 않게 설계&lt;/li&gt;
&lt;li data-end=&quot;3516&quot; data-start=&quot;3469&quot;&gt;Swift Concurrency의 방식: &lt;b&gt;이미 발생한 inversion을 runtime에서 완화&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;3521&quot; data-start=&quot;3518&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;3554&quot; data-start=&quot;3523&quot; data-ke-size=&quot;size23&quot;&gt;Task group을 이용한 패턴&lt;/h3&gt;
&lt;p data-end=&quot;3618&quot; data-start=&quot;3556&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;span data-start-index=&quot;326&quot;&gt;동시성 개수 제한 패턴 (Limiting Concurrency)&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3618&quot; data-start=&quot;3556&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3618&quot; data-start=&quot;3556&quot; data-ke-size=&quot;size16&quot;&gt;여러 Task를 동시에 실행할 때 &lt;b&gt;무작정 task를 늘리는 게 아니라 제한을 두는 방식.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;3618&quot; data-start=&quot;3556&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 아래 예시는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b data-path-to-node=&quot;11&quot; data-index-in-node=&quot;10&quot;&gt;메모리를 아끼면서(최대 3개), 속도는 최대한 뽑아내는(빈자리 즉시 채우기)&lt;/b&gt;&amp;nbsp;최적의 병렬 처리가 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1768371557117&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func chopIngredients(_ ingredients: [any Ingredient]) async -&amp;gt; [any ChoppedIngredient] {
	return await withTaskGroup(of: (ChoppedIngredient?).self, 
				returning: [any ChoppedIngredient].self) { group in
		// Concurrently chop ingredients
		let maxChopTasks = min(3, ingredients.count)
		for ingredientIndex in 0..&amp;lt;maxChopTasks {
			group.addTask { await chop(ingredients[ingredientIndex]) }
		}
		// Collect chopped vegetables
		var choppedIngredients: [any ChoppedIngredient] = []
		var nextIngredientIndex = maxChopTasks
		for await choppedIngredient in group {
			if nextIngredientIndex &amp;lt; ingredients.count {
				group.addTask { await chop(ingredients[nextIngredientIndex]) }
				nextIngredientIndex += 1
			}
			if choppedIngredient != nil {
				choppedIngredients.append(choppedIngredient!)
			}
		}
		return choppedIngredients
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;span data-start-index=&quot;657&quot;&gt;2. &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://developer.apple.com/documentation/swift/discardingtaskgroup&quot;&gt;DiscardingTaskGroup&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;span data-start-index=&quot;657&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/withdiscardingtaskgroup(returning:isolation:body:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;withDiscardingTaskGroup(returning:isolation:body:)&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div data-start-index=&quot;708&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-start-index=&quot;708&quot;&gt;결과값이 필요 없고 리소스를 즉시 해제해야 하는 대량의 작업을 처리할 때 사용한다.&lt;/div&gt;
&lt;div data-start-index=&quot;708&quot;&gt;
&lt;pre id=&quot;code_1768375896912&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func run async throws {
    try await withThrowingDiscardingTaskGroup { group in
        for cook in staff.keys {
            group.addTask { try await cook.handleShift() }
        }
        
        group.addTask { // keep the restaurant going until closing time
            try await Task.sleep(for: shiftDuration)
            throw TimeToCloseError()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start-index=&quot;755&quot;&gt;상황: 요리사들의 근무(Shift)를 관리하며, 퇴근 시간이 되면 모든 작업을 중단해야 한다.&lt;/li&gt;
&lt;li data-start-index=&quot;755&quot;&gt;&lt;b data-start-index=&quot;878&quot;&gt;특징:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start-index=&quot;755&quot;&gt;자식 작업이 완료되면 &lt;b data-start-index=&quot;893&quot;&gt;결과를 유지하지 않고 리소스를 즉시 방출&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; data-start-index=&quot;915&quot;&gt;하여 메모리 소비를 줄입니다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-start-index=&quot;755&quot;&gt;퇴근 시간 오류(&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;TimeToCloseError&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; data-start-index=&quot;957&quot;&gt;)가 발생하면 &lt;/span&gt;&lt;b data-start-index=&quot;965&quot;&gt;형제 작업들이 자동으로 취소&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; data-start-index=&quot;980&quot;&gt;되므로 명시적인 정리가 필요 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;withTaskgroup 를 쓰는거랑 뭐가 다른거지?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;withTaskGroup을 사용하면, &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;메모리 부담이 있을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3753&quot; data-start=&quot;3737&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 TaskGroup은 &quot;누군가 나중에 결과를 물어볼 수도 있어&quot;라는 가정하에 동작하는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3753&quot; data-start=&quot;3737&quot;&gt;addTask 클로저가 Void를 반환하더라도, 시스템 내부적으로는 &quot;이 작업이 성공적으로 끝났다&quot;는 상태값을 메모리에 쌓아둔다.&lt;/li&gt;
&lt;li data-end=&quot;3753&quot; data-start=&quot;3737&quot;&gt;이 정보들은 group.next()를 호출해 하나씩 꺼내 가거나, 전체 그룹 블록이 완전히 종료될 때까지 메모리에 계속 남아 있는다.&lt;/li&gt;
&lt;li data-end=&quot;3753&quot; data-start=&quot;3737&quot;&gt;만약 만 단위의 아주 많은 작업을 추가했는데 next()를 호출하지 않는다면, 완료된 작업들의 &quot;성공 기록&quot;이 메모리에 계속 누적되어 성능에 영향을 줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;따라서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;withDiscardingTaskGroup&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;는 return 이 필요없는 상황(= addTask 클로저가 Void를 반환하는 상황)에서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;많은 Task 를 돌리는 작업에서 사용하면 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;4015&quot; data-start=&quot;4012&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;4059&quot; data-start=&quot;4017&quot; data-ke-size=&quot;size23&quot;&gt;Task-local values와 context propagation&lt;/h3&gt;
&lt;p data-end=&quot;4103&quot; data-start=&quot;4061&quot; data-ke-size=&quot;size16&quot;&gt;@TaskLocal은&lt;br /&gt;처음엔 단순히 global(static) 변수처럼 보이지만, 실제로는&lt;/p&gt;
&lt;blockquote data-end=&quot;4135&quot; data-start=&quot;4105&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;4135&quot; data-start=&quot;4107&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현재 Task tree 안에서만 공유되는 값&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;4140&quot; data-start=&quot;4137&quot; data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768376626784&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@TaskLocal static var cook: String&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4259&quot; data-start=&quot;4191&quot; data-ke-size=&quot;size16&quot;&gt;부모 Task에서 값을 설정하면 자식 Task에서도 접근 가능하지만, 다른 Task tree에는 전혀 영향이 없다.&lt;/p&gt;
&lt;p data-end=&quot;4259&quot; data-start=&quot;4191&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4459&quot; data-start=&quot;4403&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;4017&quot; data-end=&quot;4059&quot; data-ke-size=&quot;size23&quot;&gt;Task traces 와 span metadata propagation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Tracing API 의 withSpan 함수에 대한 이해에 앞어 trace 와 span 에 대한 개념이 있어야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;trace는 여러개의 span 으로 구성되어있고, 아래의 예시에서는 &quot;수프 만들기&quot;가 하나의 span 이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 span 에는 `span.attributes[&quot;kitchen.order.id&quot;] = order.id` 와 같이 metadata 가 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://swiftpackageindex.com/apple/swift-distributed-tracing/1.0.1/documentation/tracing/nooptracer/withspan(_:context:ofkind:function:file:line:_:)-3iy8t&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;withSpan(_:context:ofKind:function:file:line:_:)&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768377167140&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Tracing

func makeSoup(order: Order) async throws -&amp;gt; Soup {
	try await withSpan(#function) { span in
		span.attributes[&quot;kitchen.order.id&quot;] = order.id
		async let pot = stove.boilBroth()
		async let choppedIngredients = chopIngredients(order.ingredients)
		async let meat = marinate(meat: .chicken)
		let soup = try await Soup(meat: meat, ingredients: choppedIngredients)
		return try await stove.cook(pot: pot, soup: soup, duration: .minutes(10))
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서는 async let을 사용하면 3개의 자식 Task가 생성된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;boilBroth()&lt;/li&gt;
&lt;li&gt;chopIngredients()&lt;/li&gt;
&lt;li&gt;marinate()&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;이때 Task Tree에서의&amp;nbsp;&lt;b data-path-to-node=&quot;10&quot; data-index-in-node=&quot;37&quot;&gt;Task Local Storage&lt;/b&gt;를 활용해 Span 메타데이터를 자식들에게 자동으로 넘겨준다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;즉,&amp;nbsp;각 자식 Task 에서도 order ID 값을 자동으로 알 수 있게 된다는 의미이다.&lt;/p&gt;
&lt;hr data-end=&quot;4464&quot; data-start=&quot;4461&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;정리하면서 추가로 알게 된 점은&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;structured 와 unstructured 에서의 상속/propagation 개념이 항상&amp;nbsp; 헷갈렸는데,&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같은 차이가 있었다.&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;우선&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;structured task 를 생성하는 방법에는 async let 과 TaskGroup 가 있고,&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;unstructured task 를 생성하는 방법에는 Task {} 와 Task.detached {} 가 있다.&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;그리고 각 방식의 차이는 아래와 같다:&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;structured task 는 상위 Task 로 부터 아래를 상속받는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4636&quot; data-start=&quot;4600&quot;&gt;Task Cancellation&lt;/li&gt;
&lt;li data-end=&quot;4636&quot; data-start=&quot;4600&quot;&gt;Error Propagation&lt;br /&gt;: 자식 작업에서 에러가 발생하면, 그 에러가 부모에게 전달(전파)되고, 부모는 나머지 모든 자식 작업을 자동으로 취소된다.&lt;/li&gt;
&lt;li data-start=&quot;4293&quot; data-end=&quot;4303&quot;&gt;Priority Progatation&lt;/li&gt;
&lt;li data-start=&quot;4600&quot; data-end=&quot;4636&quot;&gt;Context Progatation (ex: @TaskLocal, Actor)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4636&quot; data-start=&quot;4600&quot; data-ke-size=&quot;size16&quot;&gt;unstructured task 에서는 Task {} 인지, Task.detached {} 인지에 따라 상위 Task 로 부터 다르게 상속받는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;4277&quot; data-end=&quot;4401&quot;&gt;
&lt;li data-start=&quot;4277&quot; data-end=&quot;4353&quot;&gt;Task {}
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;4293&quot; data-end=&quot;4353&quot;&gt;
&lt;li data-end=&quot;4303&quot; data-start=&quot;4293&quot;&gt;Priority Progatation&lt;/li&gt;
&lt;li data-end=&quot;4636&quot; data-start=&quot;4600&quot;&gt;Context Progatation (ex: @TaskLocal, Actor)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-start=&quot;4355&quot; data-end=&quot;4401&quot;&gt;Task.detached {}
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;4355&quot; data-end=&quot;4401&quot;&gt;(아무것도 상속되지 않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Task Cancellation 방식에 대해 더 알아보고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Structured 와 Unstructured 를 활용하는 방법에 대해 더 자세히 알아봤다.&lt;br /&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;4848&quot; data-start=&quot;4845&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유익했다면 댓글/공감 남겨주세요~~ 작성자에게 큰 힘이 됩니다 ☺️&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>Swift</category>
      <category>SWiFT</category>
      <category>swift concurrency</category>
      <category>TASK</category>
      <category>WWDC23</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/41</guid>
      <comments>https://red-cherry-ring.tistory.com/41#entry41comment</comments>
      <pubDate>Wed, 14 Jan 2026 17:16:02 +0900</pubDate>
    </item>
    <item>
      <title>Swift Concurrency #2 - Structured Concurrency와 Task</title>
      <link>https://red-cherry-ring.tistory.com/40</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=nyuq9qc-2H8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WWDC21:&amp;nbsp;Explore&amp;nbsp;structured&amp;nbsp;concurrency&amp;nbsp;in&amp;nbsp;Swift&amp;nbsp;|&amp;nbsp;Apple&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=nyuq9qc-2H8&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/br8yMu/dJMb84XWdXk/bMQew0dYdJVu23Q87ResxK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/k2unz/dJMb9c9vmHR/Nc7ohrKVEOM9w0W30Z8G9k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;WWDC21: Explore structured concurrency in Swift | Apple&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/nyuq9qc-2H8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift Concurrency 를 공부하다보면 async-await 외에도 자주 접하는게 Task 라는 녀석이다. 그런데 이게 익숙하지 않던 개념이어서 그런지 확실히 머릿속에 정리하기가 쉽지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Task { } ?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 한번이라도 Swift Concurrency 를 시도했던 사람이라면 &lt;b&gt;Task {}&lt;/b&gt; 를 본적이 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 initializer의 용도는 &quot;비동기 코드(async 함수)를 실행하기 위한 새로운 실행 컨텍스트를 제공한다.&quot; 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 작성되어있던 프로젝트 코드에서 async 함수를 호출하고 싶을 때 냅다 그냥 async 함수를 호출하면 오류가 나는데, async 함수를 &lt;b&gt;Task {}&lt;/b&gt; 로 한번 감싸주면 async 함수를 실행시킬 수 있는 컨텍스가 생성되기 때문에 오류가 사라진다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;보통 우리는 동기 코드 혹은 비동기 closure 내에서 async 함수를 호출하기 위해 &lt;b&gt;Task {}&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 &lt;/span&gt;사용하곤 한다.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;하지만 오류가 쉽게 해결된다고해서 막무가내로 사용하면... 안된다... (나는 몰랐지 이렇게 복잡하고 알아야할 게 많은 줄은...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Task 는 structured 와 unstructured 로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 structured 와 unstructured 의 차이점을 알아보고 각각을 어떻게 생성해서 사용할 수 있는지 알아보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Structured Task 와 Unstructured Task&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Task는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비동기 코드를 실행하며 Combine 과 마찬가지로 cancel 기능을 제공한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Task 는 여러개 생성될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'특정 비동기 로직이 끝나면 연결해서 진행되어야하는 비동기 로직이 있고, 또 연결되어있는 비동기로직이 있다.' 라고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 비동기 로직이 취소되면 연결되어있는 하위 비동기 로직들은 필요가 없기 때문에 같이 취소되는게 성능상 효율적이다. 이를 위해 &lt;b&gt;Structured Task(Task&amp;nbsp;hierarchy)&lt;/b&gt; 라는 개념이 있는 것이다. Task hierarchy 가 형성되면 취소 전파(&lt;b&gt;cancellation propagation&lt;/b&gt;)가 가능하고, 이는 연계되어있는 여러 Task 를 관리하기에 용이하다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(* 참고: Task Hierarchy 를 도식화 한 표현으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Task Tree&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;라는 용어로 쓰이기도 한다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unstructured Task는 structured Task가 아닌 나머지를 unstructured 라고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0e0e0e;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;structured Task 생성하려면, &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;async let&lt;/b&gt;을 쓰거나 &lt;b&gt;withThrowingTaskGroup&lt;/b&gt;을 사용하면 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;unstructured Task 를 생성하려면, &lt;b&gt;Task {}&lt;/b&gt; 나 &lt;b&gt;Task.detached {}&lt;/b&gt; 로 생성하면 된다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Structured Task&lt;/h2&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;h3 style=&quot;color: #004fa8;&quot; data-heading=&quot;Async-let tasks&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;async let&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 비동기 코드가 아래 코드 예시와 같다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;첫번째 비동기 호출인 await &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;URLSession.shared.data(for:&amp;nbsp;imageReq)&lt;/span&gt; 가 return 되어야만 &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;await&amp;nbsp;URLSession.shared.data(for:&amp;nbsp;metadataReq)&lt;/span&gt; 를 요청할 수 있고, &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;await URLSession.shared.data(for: metadataReq) 가 return 되어야만 guard 문에 도달할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;즉 아래 코드는 sequential 하게 처리된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;pre id=&quot;code_1759305564309&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let (data, _) = try await URLSession.shared.data(for: imageReq)
let (metadata, _) = try await URLSession.shared.data(for: metadataReq)

guard let size = parseSize(from: metadata),
	  let image = UIImage(Data: data)?.byPreparingThubnail(ofSize: size) else {
	  throw SomError()
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 두 비동기 로직을 동시에 실행시킬 수 없다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&amp;nbsp;&lt;b&gt;async let&lt;/b&gt;을 사용하면,&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;pre id=&quot;code_1759305579747&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async let (data, _) = URLSession.shared.data(for: imageReq)
async let (metadata, _) = URLSession.shared.data(for: metadataReq)

guard let size = parseSize(from: try await metadata),
	  let image = try await UIImage(Data: data)?.byPreparingThubnail(ofSize: size) else {
	  throw SomError()
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 Task Hierarchy 를 구성하며 두 비동기 로직을 동시에 실행시킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1759323898110&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Parent Task // 현재 함수 실행 컨텍스트
│
├─ Child Task // URLSession.shared.data(for: imageReq)
│
└─ Child Task // URLSession.shared.data(for: metadataReq)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 설명은 아래와 같다&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;0&quot;&gt;async-let 을 만나면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;URLSession.shared.data(for:&amp;nbsp;imageReq)&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;비동기 로직을 위한&amp;nbsp;&lt;/span&gt;&lt;/span&gt;child task를 생성한다.&lt;br /&gt;(task 는 concurrent하게 비동기 로직을 실행할 수 있다)&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;&lt;span&gt;return 값&lt;/span&gt;에는 placeholder 를 할당해두고 다음 코드로 넘어간다.&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;URLSession.shared.data(for: metadataReq) 비동기 로직도 1~2번과 동일하게 반복된다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;await metadata 에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;metadata&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;리턴하는&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;child task 가 완료될 때까지 suspend 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;await UIImage(Data: data)?.byPreparingThubnail(ofSize: size)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;data 값을 리턴하는&amp;nbsp;&lt;/span&gt;child task 가 완료될 때까지 suspend 한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #004fa8;&quot; data-ke-size=&quot;size23&quot; data-heading=&quot;Async-let tasks&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Group Task&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async let 보다 유연하게 사용할 수 있으면서, Task Hierarchy 를 생성하는 방법으로는 &lt;a href=&quot;https://developer.apple.com/documentation/swift/withthrowingtaskgroup(of:returning:isolation:body:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;withThrowingTaskGroup&lt;/b&gt;&lt;/a&gt;가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;async let 만 사용한 아래코드를 보면, &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;data와 metadata&amp;nbsp;모두 반환되어야지만 다음 for문을 돌 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759306913833&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func fetchThumbnail(for ids: [String]) async throws -&amp;gt; [String: UIImage] {
	var thumbnails: [String: UIImage] = [:]
	for id in ids {
		thumbnails[id] = try await fetchOneThumbnail(withID: id)
	}
	return thumbnails
}

func fetchOneThumbnail(withID id: String) async throws -&amp;gt; UIImage {
	// ...
	async let (data, _) = URLSession.shared.data(for: imageReq)
	async let (metadata, _) = URLSession.shared.data(for: metadataReq)
	// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 withThrowingTaskGroup 을 사용하면 모든 비동기 로직을 한번에 실행시킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1759307018384&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func fetchThumbnail(for ids: [String]) async throws -&amp;gt; [String: UIImage] {
	var thumbnails: [String: UIImage] = [:]
	try await withThrowingTaskGroup(of: Void.self) { group in
		for id in ids {
			group.async {
				return(id, try await fetchOneThumbnail(withID: id))
			}
		}
		
		for try await (id, thumbnail) in group {
			thumbnails[id] = thumbnail
		}
	}
	return thumbnails
}

func fetchOneThumbnail(withID id: String) async throws -&amp;gt; UIImage {
	// ...
	async let (data, _) = URLSession.shared.data(for: imageReq)
	async let (metadata, _) = URLSession.shared.data(for: metadataReq)
	// ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세히 설명하자면 아래와 같이 동작하는 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #0e0e0e; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;&lt;b&gt;withThrowingTaskGroup&lt;/b&gt; 내부에서 &lt;b&gt;group.async {}&lt;/b&gt; 를 호출해 child task&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;를 생성한다.&lt;br /&gt;(task 는 concurrent하게 비동기 로직을 실행할 수 있다)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;생성된 child task 는 &lt;b&gt;withThrowingTaskGroup&lt;/b&gt; scope 내에서만 유효하며, &lt;b&gt;withThrowingTaskGroup &lt;/b&gt;내의&amp;nbsp;모든 child task 가 완료되면,&amp;nbsp;&lt;b&gt;await withThrowingTaskGroup&lt;/b&gt; 이 resume 된다.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 주의할 점은 group.async 안에서 thumbnails 를 직접 수정하지 않고, 튜플을 return 한 이유는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0e0e0e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;0&quot;&gt;group.async 내부(= child task)에서는 mutual variable 에 직접 접근하지 말고, return 만 한다. 왜냐하면 &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;2개 child task 들이 동시에 Dictionary 를 수정하려고 하면 크래시가 발생하거나 data race 가 발생할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;group.async 외부(= parent task)에서 for await 을 사용해 결과값들은 iterate 할 수 있다.&lt;br /&gt;(호출된 순이 아닌, 완료된 child task 순으로 iterate 된다)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;2&quot;&gt;&lt;b&gt;for await&lt;/b&gt; 은 하나씩 await 했다가 호출되므로, 안전하게 Dictionary 에 값을 수정할 수 있다.&lt;br /&gt;(AsyncSequence 프로토콜을 차용한다면 언제든지 for await 을 사용할 수 있다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;자 이제 structured Task 를 어떻게 생성하는지 알게 됐으니 아래 예시 코드들을 보면서&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt; Task Tree(Task Hierarchy)에서 cancel 과 에러처리가 실제로 어떻게 동작하는지 자세히 알아보도록 하자.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Task Tree 에서의&amp;nbsp;Cancellation Propagation 과&amp;nbsp;Error 처리&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e;&quot;&gt;Task Tree 에서 부모 Task 가 취소되는 경우, 자식 Task(하위 비동기 로직)들에게 취소되었음을 전파(&lt;b&gt;cancellation propagation&lt;/b&gt;)시키기 때문에, 부모 Task를 취소하는 것만으로 모든 자식 Task들을 취소를 시킬 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e;&quot;&gt;또한 Task Tree 내에서 에러 throwing 이 발생하면, Tree 내의 모든 task가 영향을 받으며 최종적으로 parent task 에 error가 throw 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0e0e0e;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;cancellation propagation&lt;/b&gt; 에 대해&amp;nbsp;좀 더 구체적으로 알아보자.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;pre id=&quot;code_1759312146550&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func doSomething() async throws {
    async let (data, _) = URLSession.shared.data(for: imageReq)
    async let (metadata, _) = URLSession.shared.data(for: metadataReq)

    guard let size = parseSize(from: try await metadata),
          let image = try await UIImage(Data: data)?.byPreparingThubnail(ofSize: size) else {
          throw SomError()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 parent task 는 data, metadata 데이터를 비동기로 가져오기위한 child task 두개를 갖는다.&lt;br /&gt;이 두개의 child task 가 모두 완료 되어야 parent task 도 종료될 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 위 코드에서 doSomething() 을 수행하던 Task 가 취소된다면 어떻게 될까?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;doSomething 내에서 생성된 두개의 child task 로 parent task 가 취소되었음이 전달될 것이다.&lt;/li&gt;
&lt;li&gt;그러면 각각의 child task 는 cancelled 로 표기된다. 하지만 cancelled 로 표기되었다고 해서 실제로 task 가 바로 멈추는건 아니며, 해당 비동기 로직의 return 을 체크할 필요가 없음을 명시하는 것이라고 이해하면 된다.&lt;/li&gt;
&lt;li&gt;모든 child task 가 cancelled 되면 parent task scope 를 빠져나올 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그런데... task 가 cancelled 표기되었는지 어떻게 알 수 있을까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(* 참고 : Task cancellation 은 async context 인지 아닌지와 상관없이 어디서나 체크 가능하다)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;try Task.checkCancellation()&lt;/b&gt; : error throwing 으로 cancel되었음을 알 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;Task.isCancelled&lt;/b&gt; : cancel 여부를 Boolean 값으로 알 수 있다.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(&lt;b&gt;withTaskCancellationHandler&lt;/b&gt; 를 사용하는 방법도 있는데 이건 다음 시간에...ㅎㅎ)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1759321623531&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func fetchThumbnail(for ids: [String]) async throws -&amp;gt; [String: UIImage] {
	var thumbnails: [String: UIImage] = [:]
	for id in ids {
		try Task.checkCancellation() // or `if Task.isCancelled { break }`
		thumbnails[id] = try await fetchOneThumbnail(withID: id)
	}
	return thumbnails
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;흠.. 그렇다면 이번엔 취소가 아닌 child task 중 하나에서 에러가 발생한다면 어떻게 될까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;위 예시 코드의&amp;nbsp;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e;&quot;&gt;URLSession.shared.data(for: metadataReq) 비동기 로직에서&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;에러가 발생했다고 가정하자.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;0&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;URLSession.shared.data(for: metadataReq) 를 수행하던 child task 는 error 를 throw 하면서&lt;/span&gt;&amp;nbsp;종료되고,&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;아직 resume 되지 않은 &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;URLSession.shared.data(for: imageReq) 를 처리하던 child task 는&lt;/span&gt;&amp;nbsp;cancelled task 로 표기된다.&lt;br /&gt;(cancelled 로 표기했다고 바로 종료되는건 아님. 단지 결과값이 더이상 필요없다고 표기하는것 뿐)&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;3&quot;&gt;2번에서 cancelled task 로 표기되면 Cancellation Propagation 에 의해 그 하위 task 들도 자동으로 cancelled 표기된다.&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;4&quot;&gt;계층 내의 모든 하위 tasks 들이 종료되면, 1번에서 발생했던 error 가 parent task 로 throw  되면서 parent task 도 종료된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e;&quot;&gt;즉,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;같은 Task Tree 내에 있다면 (즉, structured task 라면) cancellation, error handling 모두 같이 관리 된다는 의미이다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Structured Task 가 무엇인지 어떻게 사용하는 것인지를 알아봤다. 마지막으로 async let 과 group task 을 비교하면서 마무리하겠다.&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;h3 style=&quot;color: #004fa8;&quot; data-ke-size=&quot;size23&quot; data-heading=&quot;Async-let tasks&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;async let 과 Group Task 비교하기&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;0&quot;&gt;공통점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;async-let 과 group task 모두 &lt;/span&gt;child task 에서 에러가 발생했다면, parent task 로 에러가 throw 되고, 모든 child task 들은 묵시적으로 cancelled 로 표기된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;3&quot;&gt;차이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;4&quot;&gt;group task 는&lt;span&gt;&amp;nbsp;&lt;/span&gt;cancelAll()&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드 를 사용해 모든 child task 들을 수동으로 취소할 수 있다.&lt;br /&gt;(정상적인 종료 상황에서 모든 태스크를 즉시 취소하고 싶을 때 사용)&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;6&quot;&gt;async-let 은 이런 기능이 제공되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Unstructured Task&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: left;&quot;&gt;그렇지만 모든&amp;nbsp;상황에서&amp;nbsp;structured&amp;nbsp;task&amp;nbsp;를&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있는건&amp;nbsp;아니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;non-async 콘텍스트에서 async 함수를 호출할 땐, parent task 자체가 없을 수 있다.&lt;/li&gt;
&lt;li&gt;task 의 범위가 scope 외에 존재하길 바랄 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: left;&quot;&gt;이럴때 사용하는게 Unstructured Task 이다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: left;&quot;&gt;위에서 잠깐 언급했듯이 &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;unstructured Task 를 생성하려면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Task {}&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Task.detached {}&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 생성하면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #006dd7; text-align: left;&quot;&gt;Task { }&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 Task{} 에 대한 설명을 하자면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시작 컨텍스트의 actor와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;동일한 actor 에서 동작한다(= actor context 를 상속). 또한&lt;/span&gt;&amp;nbsp;상위 task 의 priority 및 기타 특성도 상속한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Task {} 로 생성된 task 는 task 가 생성된 scope 밖의 범위에서도 수명이 유지된다. (= &lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;unscoped&amp;nbsp;lifetime&lt;/span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Task {} 는 non-async context 에서 호출할 수 있다.&lt;/li&gt;
&lt;li&gt;자유도가 높은 대신 cancellation/error propagation 은 지원되지 않으며, 개발자가 직접 관리해주어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 올바른 Task 사용 예시는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1759322594391&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MainActor
class MyDelegate: UICollectionViewDelegate {
	var thumbnailTasks: [IndexPath: Task&amp;lt;Void, Never&amp;gt;] = [:]
	
	func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
		let ids = getThumbnailIDs(for: item)
		thumbnailTasks[item] = Task {
			defer { thumbnailTasks[item] = nil }
            do {
                let thumbnails = try await fetchThumbnails(for: ids)
                display(thumbnails, in: cell)
            } catch {
            	// handle error...
			}
		}
	}
	
	func collectionView(_ view: UICollectionView, didEndDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
		thumbnailTasks[item]?.cancel()
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;이때, Task 는 actor 속성을 그래도 상속받으므로, 메인스레드에서만 실행된다.&lt;br /&gt;따라서 Task {} 내에서 thumbnailTasks 에 접근해도 data race 가 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Task.detached { }&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 봤듯이 Task {} 는 시작 컨텍스트의 actor, priority 등을 상속받는다. 하지만 이 중에서 아무것도 상속받지 않길 바랄때 사용하는게 detached task 이다!&lt;br /&gt;다만 얘도 Task {} 와 마찬가지로 unscoped lifetime 을 가지고, 직접 cancelled, error, awaited 를 관리해줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;background&amp;nbsp;priority&amp;nbsp;를&amp;nbsp;갖는&amp;nbsp;여러&amp;nbsp;작업이&amp;nbsp;수행되어야할&amp;nbsp;땐?&lt;br /&gt;detached task 안에 structured task 를 구성하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1759322649393&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MainActor
class MyDelegate: UICollectionViewDelegate {

	func collectionView(_ view: UICollectionView,
						willDisplay cell: UICollectionViewCell,
						forItemAt item: IndexPath) {
		let ids = getThumbnailIDs(for: item)
		thumbnailTasks[item] = Task {
			defer { thumbnailTasks[item] = nil }
			let thumbnails = await fetchThumbnails(for: ids)
			
			Task.detached(priority: .background) {
				// unstructured 안에 structured 구성 가능!
				withTaskGroup(of: Void.self) { g in
					g.async { writeToLocalCache(thumbnails) }
					g.async { log(thumbnails) }
					g.async { ... }
				}
			}
			
			display(thumbnails, in: cell)
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task.detached 자체는 unstructured task 이지만 detached task 내부에서는 child task 들이 생성되었으므로 detached task 를 cancel 하면, 그 안의 모든 child task 다 cancel 이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Task 를 생성하는 법과 그 차이점에 대해 모두 살펴보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Task 생성하는 방법을 정리하고, 비교한 표를 첨부하면서 마무리하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- structured task 는 cancellation, error handling 이 연관되어 동작하며, priority 와 actor context 를 상속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- unstructured task 인 Task {} 는 priority 와 actor 를 상속할 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- unstructured task 인 Task.detached {} 는 아무것도 상속하지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2042&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byIlCY/btsQ1kMq6kw/PKQut578Z9Uh1LKaP8Yr6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byIlCY/btsQ1kMq6kw/PKQut578Z9Uh1LKaP8Yr6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byIlCY/btsQ1kMq6kw/PKQut578Z9Uh1LKaP8Yr6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyIlCY%2FbtsQ1kMq6kw%2FPKQut578Z9Uh1LKaP8Yr6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2042&quot; height=&quot;638&quot; data-origin-width=&quot;2042&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유익했다면 댓글/공감 남겨주세요~~ 작성자에게 큰 힘이 됩니다 ☺️&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Swift</category>
      <category>SWiFT</category>
      <category>swift concurrency</category>
      <category>TASK</category>
      <category>WWDC21</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/40</guid>
      <comments>https://red-cherry-ring.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 1 Oct 2025 23:14:10 +0900</pubDate>
    </item>
    <item>
      <title>Swift Concurrency #1 - Async와 Await</title>
      <link>https://red-cherry-ring.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=r_clm92NI1s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WWDC21:&amp;nbsp;Meet&amp;nbsp;async/await&amp;nbsp;in&amp;nbsp;Swift&amp;nbsp;|&amp;nbsp;Apple&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=r_clm92NI1s&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/culzj3/dJMb84p6c05/WkJ4PkWopLCFQG8PHveNfK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/iox8T/dJMb8VNsP9H/x7Tm3JXEYPb4UkHh0iJXtk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;WWDC21: Meet async/await in Swift | Apple&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/r_clm92NI1s&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #08377d; text-align: start;&quot; data-heading=&quot;Async/Await 등장 배경&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Async/Await 등장 배경&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Swift Concurrency를 사용하려면, async-await 를 알아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러려면 &lt;b&gt;동기&lt;/b&gt;vs&lt;b&gt;비동기&lt;/b&gt; 코드의 차이를 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기 코드와 비동기 코드는 iOS를 개발해봤다면 모를 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 일반적인 코드 작성 방식이 동기 코드를 작성하는 방법이고, 나중에 이벤트가 발생하면 호출할 코드는 따로 closure 의 형태로 작성하는데 이게 비동기 코드이다. 그래서 보통 오래 걸리는 작업은 비동기 코드를 사용해 언젠지 모르지만 이벤트가 발생하면 그때 처리하도록 하고, 나머지의 일반적인 케이스에서는 동기 코드로 작성하곤한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 URLSession 의 dataTask 함수가 대표적인 비동기 함수이다. (API response 가 와야 completionHandler 가 호출된다)&lt;/p&gt;
&lt;pre id=&quot;code_1758950263951&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func dataTask(
    with request: URLRequest,
    completionHandler: @escaping (Data?, URLResponse?, (any Error)?) -&amp;gt; Void
) -&amp;gt; URLSessionDataTask&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데... completionHandler 와 같은 구조의 비동기코드를 짜면서 이슈가 있었던게...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 나는등의 강제성이 없다보니 아래 예시처럼 completion 을 누락하기가 쉽다는 점이었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;completion 이라는 건, 애초에 비동기 작업이 완료되면 호출되도록 설계되었다는 건데, 이게 누락되면 문제인게 아래 코드 예시로 설명하면 fetchThumbnail 의 completion이 호출되면(= 썸네일 로드가 완료/실패하면) 로딩 인디케이터를 화면에서 제거해라.. 라는 로직이 있었다고 치면, 아래 예시에서는 로딩 인디케이터가 평생 제거되지 않을 수 있게 된다는 뜻이기 때문이다..!&lt;/p&gt;
&lt;pre id=&quot;code_1758950809715&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -&amp;gt; Void) {
	let request = thumbnailURLRequest(for: id)
	let task = URLSession.shared.dataTask(with: request) { data, response, error in
		if let error {
			completion(nil, error)
		} else if (response as? HTTPURLResponse)?.statusCode != 200 {
			completion(nil, FetchError.badID)
		} else {
			guard let image = UIImage(data: data!) else {
				return // ⚠️ 누락!!
			}
			image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
				guard let thumbnail else {
					return // ⚠️ 누락!!
				}
				completion(thumbnail, nil)
			}
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 뭣보다 closure 구조를 쓰면, 비동기 코드에서 발생하는 에러를 함수를 통해 Error Throwing 으로 전달할 수가 없게 되고, 결국 completion 에 Error 타입을 추가해 전달할 수 밖에 없다는 불편함이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비동기 작업이 종료되었음을 누락없이(= 안전하게) 전달&lt;/b&gt;하고, &lt;b&gt;함수의 throw 기능도 활용 가능&lt;/b&gt;하게 한 방법이!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift Concurrency 의 async-await 구조이다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(추가로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;비동기 로직을 여러개 연결했을 때&lt;span&gt; 이전에는&amp;nbsp;&lt;/span&gt;&lt;/span&gt;closure 안에 closure 안에 closure... 이렇게 구현했어야했는데 async-await 을 쓰면 nested 방식이 아니어서 가독성도 훨씬 좋아진다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 본격적으로 async-await 이 어떻게 사용되는지 확인해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 closure로 구현했었던 fetchThumbnail 함수를 async-await 을 사용해 구현하면 아래와 같아진다.&lt;/p&gt;
&lt;pre id=&quot;code_1758951873873&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func fetchThumbnail(for id: String) async throws -&amp;gt; UIImage {
	let request = thumbnailURLRequest(for: id)
	let (data, response) = try await URLSession.shared.data(for: request)
	guard (response as? HTTPURLResponse)?.statusCode == 200 else { 
		throw FetchError.badID
	}
	let maybeImage = UIImage(data: data)
	guard let thumbnail = await maybeImage?.thumbnail else { throw FetchError.badImage }
	return thumbnail
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;completion 를 개발자가 의식적으로 호출하는 것이 아닌 비동기 작업이 종료되면 함수 return을 통해 전달 받을 수 있게 되면서 누락되는 케이스가 제거되었고, completion 파라미터에 포함되어 있던 Error 는 try-throws 로 전달하게된걸 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(그리고 nested 구조가 아니어서 가독성도 훨씬 좋다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Await&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 await 은 뭘까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await 라는 건 await 키워드가 있는 위치에서 코드가 일시중단(suspend)될 수 있음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시를 통해 await 키워드가 없을 때(동기 코드)와 있을 때(비동기 코드)의 차이점을 비교하면서 일시중단된다는게 어떤 의미인지 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 아래와 같이 동기 코드였다면, 아래 순서로 중단없이 쭉쭉 진행될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. doSomething() 을 실행하던 하던 스레드가 A 작업을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. doMore() 함수로 스레드 제어권을 넘긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. doMore() 함수의 B 작업이 완료되면 해당 함수가 return 되면서 스레드가 doSomething() 함수로 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. C 작업을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1758952465814&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func doSomething() {
	// A...
	doMore()
	// C...
}

func doMore() {
	// B...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 await 키워드를 사용한 비동기 코드는 아래와 같이 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. doSomething() 을 실행하던 하던 스레드가 A 작업을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. doMore() 함수는 to-do list 에 저장되고, doSomething() 함수는 &lt;b&gt;중단(suspend)&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 시스템은 to-do list에서 우선순위가 높은 작업 순으로 스레드를 할당하고, 언젠가 B 작업이 완료되면 doMore() 함수가 return 되면서 doSomething() 함수는 &lt;b&gt;재개(resume)&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. C 작업을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1758952908702&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func doSomething() async {
	// A...
	await doMore()
	// C...
}

func doMore() async {
	// await B...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 await 은 suspend/resume 될 수 있음을 명시하고 각각의 의미는 아래와 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;0&quot;&gt;&lt;b&gt;중단(suspend)&lt;/b&gt;: thread 를 시스템에 넘겨줌&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot; data-line=&quot;1&quot;&gt;&lt;b&gt;재개(resume)&lt;/b&gt;: thread 를 다시 넘겨 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 아래를 주의하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;await 을 만났다고 반드시 suspend 되는건 아니다. (suspend 없이 진행될 수도 있다)&lt;/li&gt;
&lt;li&gt;suspend 되기 전에 코드를 실행하던 스레드와 resume 된 후에 코드를 실행하는 스레드가 다를 수 있다.&lt;/li&gt;
&lt;li&gt;suspend 된다는 건 스레드가 block 되는게 아닌, 스레드가 다른 작업을 할 수 있도록 풀어준다는 뜻이다.&lt;/li&gt;
&lt;li&gt;비동기 작업은 언제 완료 될지 알 수 없으며, 다른 비동기 작업이 먼저 종료될 수도 있다. (비동기 완료 순서 보장 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 위처럼 함수에서 async-await 을 사용하는 방법도 있지만 다른 활용법들도 있다.&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #0e63b9; text-align: start;&quot; data-heading=&quot;(Async sequences)&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Async properties&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;setter 없이 getter 만 정의되어있는 프로퍼티는 async 로 구현될 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;Swift 5.5 부터는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;async throws&lt;span style=&quot;background-color: #ffffff; color: #0e0e0e; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;도 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758954166877&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension UIImage {
	var thumbnail: UIImage? {
		get async {
			let size = CGSize(width: 40, height: 40)
			return await byPreparingThumbnail(ofSize: size)
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #0e63b9; text-align: start;&quot; data-heading=&quot;(Async sequences)&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AsyncSequence&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for await 를 사용해 AsyncSequence에서 값을 하나씩 꺼내면서, 각 값이 준비될 때까지 기다리는 반복문을 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1758954235502&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import StoreKit

func observeTransactions() async {
    for await result in Transaction.updates {
    	// result 를 이용해 처리...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유익했다면 댓글/공감 남겨주세요~~ 작성자에게 큰 힘이 됩니다 ☺️&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Swift</category>
      <category>async-await</category>
      <category>SWiFT</category>
      <category>swift concurrency</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/38</guid>
      <comments>https://red-cherry-ring.tistory.com/38#entry38comment</comments>
      <pubDate>Sat, 27 Sep 2025 15:30:43 +0900</pubDate>
    </item>
    <item>
      <title>Swift Concurrency #0 - 시작하기 전에</title>
      <link>https://red-cherry-ring.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 Swift Concurrency 가 처음 나왔을 때 도입을 시도했었다. 그런데&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;async 가 연결연결 되는 구조이다 보니까 수정범위가 꽤 많았고, 나도 그때 당시 Swift Concurrency를 완벽히 알고 있던 상황은 아니어서 사이드이펙트 없이 수정할 수 있겠다는 자신이 없었다. 또 이게 화면에 보이는 작업이 아닌 뒷단에서 로직을 개선하는 작업이라 회사입장에서 급하게 적용해야 하는 사항도 아니었고... 등등 다양한 회사 사정도 껴있긴 한데.. 아무튼 이런저런 이유로 당장 프로젝트에 적용하는 건 미루기로 팀 내에서 얘기가 됐었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회사 동료한테 Swift Concurrency 스터디 진행한다는 얘길듣고 그 스터디방에서 야금야금 같이 공부하고 있었는데, 이번 프로젝트에 Swift Concurrency를 적용해 보는 것으로 결정되면서 이젠 진짜 본격적으로 현업에서 사용할 수 있는 수준의 지식이 필요했다. 그래서 내 나름대로 이것저것 시도해 보고 실제로 프로젝트에 적용해 보면서 배웠던 내용들을 정리해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트는 현재 진행중이고, 나도 아직 공부 중이다 ㅎㅎ 공부할게 꽤 많아서 긴 여정이 될듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표는 1주 마다 1 애플 세션 듣고 1 포스팅하기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(따로 공부하고 있는것도 있어서 힘들 것 같긴 한데... 여기 이렇게 공표해 놓으면 미래의 내가 하겠지 라는 생각 중 ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서사가 길었지만 Swift Concurrency 를 처음 공부했을 때, 개념이 잘 안 익혀져서 어려웠던 기억이 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진지하게 공부하기 시작하고 나서야 알았지만(스터디 덕분에 강제성이 좀 생겼던 게 나한텐 효과가 좋았다), Swift Concurrency 애플 영상을 한번 보고 바로 알아듣긴 어려운 내용인 게 맞고 (물론 나만 그럴 수도 있긴 하다 ㅎ) 처음엔 힘들지만 잘 모르겠어도 Swift Concurrency 관련 다른 영상들도 두루두루 봐봐야 아~ 저번 영상에서 말한 게 이런 뜻이었구나 싶으면서 점점 익숙해지는 느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아무튼~~&lt;span&gt; 오늘도 Swift Concurrency 공부 화이팅!!&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;유익했다면 댓글/공감 남겨주세요~~ 작성자에게 큰 힘이 됩니다 ☺️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Swift</category>
      <category>iOS Developer</category>
      <category>SWiFT</category>
      <category>swift concurrency</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/37</guid>
      <comments>https://red-cherry-ring.tistory.com/37#entry37comment</comments>
      <pubDate>Sat, 27 Sep 2025 14:02:48 +0900</pubDate>
    </item>
    <item>
      <title>Attribute Wrapper</title>
      <link>https://red-cherry-ring.tistory.com/35</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;@frozen&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@frozen 이 붙은 enum 은 더이상 추가 case 가 없음 을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;frozen enum 의 경우 아래 예시에서 @unknown default 케이스를 고려할 필요가 없어진다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;switch enumType {
    case .enum1: ...
    @unknown default: ...
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Swift</category>
      <author>빨간체리반지</author>
      <guid isPermaLink="true">https://red-cherry-ring.tistory.com/35</guid>
      <comments>https://red-cherry-ring.tistory.com/35#entry35comment</comments>
      <pubDate>Thu, 1 Aug 2024 09:00:38 +0900</pubDate>
    </item>
  </channel>
</rss>