<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>졀니의 개발 공부✨</title>
    <link>https://iwntberich.tistory.com/</link>
    <description>  iOS 개발</description>
    <language>ko</language>
    <pubDate>Fri, 7 Aug 2026 19:26:51 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>김졀니</managingEditor>
    <image>
      <title>졀니의 개발 공부✨</title>
      <url>https://tistory1.daumcdn.net/tistory/4142616/attach/4beb8de614c24207a3ae7d61aba3d854</url>
      <link>https://iwntberich.tistory.com</link>
    </image>
    <item>
      <title>[iOS/WWDC23] SwiftData 마이그레이션</title>
      <link>https://iwntberich.tistory.com/106</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Migration&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티를 수정하거나 추가 및 삭제를 수행하게 되면 새 버전을 출시할 때 마이그레이션 과정을 거쳐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftData는 이러한 마이그레이션 과정을 간단하게 처리할 수 있는 도구를 제공한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;마이그레이션 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1️⃣ VersionSchema를 통해 모델 캡슐화&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;SwiftData 모델이 변경된 앱의 새 버전을 출시할 때 마다 새로운 &lt;code&gt;VersionedSchema&lt;/code&gt;를 정의한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;enum TripSchemaV1: VersionedSchema {
    static var versionIdentifier: Schema.Version
    static var models: [any PersistentModel.Type]
}&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;code&gt;VersionedSchema&lt;/code&gt; 는 각 버전의 스키마를 캡슐화 한다.&lt;/li&gt;
&lt;li&gt;각 버전이 &lt;code&gt;VersionedSchema&lt;/code&gt; 로 정의되어야 버전 사이에 어떤 변화가 있었는지 알 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;versionIdentifier&lt;/code&gt; 로 해당 버전 정보를 설정하고, &lt;code&gt;models&lt;/code&gt; 에 현재 버전에 포함된 모델을 설정한다.&lt;/li&gt;
&lt;li&gt;열거형 내부에 해당 버전의 스키마를 포함한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qIC5p/btsMwqwUxcb/EAJGknauPyEHHl5TmQKtyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qIC5p/btsMwqwUxcb/EAJGknauPyEHHl5TmQKtyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qIC5p/btsMwqwUxcb/EAJGknauPyEHHl5TmQKtyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqIC5p%2FbtsMwqwUxcb%2FEAJGknauPyEHHl5TmQKtyk%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;483&quot; height=&quot;427&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&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;2️⃣ SchemaMigraionPlan 생성&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;code&gt;SchemaMigrationPlan&lt;/code&gt; 을 통해 마이그레이션 과정을 정의한다.&lt;/li&gt;
&lt;li&gt;SwiftData가 필요한 마이그레이션을 순서대로 수행하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB8KOy/btsMwAsIahU/BsCJsEvluXeDGF8sdphQ21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB8KOy/btsMwAsIahU/BsCJsEvluXeDGF8sdphQ21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB8KOy/btsMwAsIahU/BsCJsEvluXeDGF8sdphQ21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB8KOy%2FbtsMwAsIahU%2FBsCJsEvluXeDGF8sdphQ21%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;555&quot; height=&quot;418&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/figure&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;code&gt;schemas&lt;/code&gt; 로 포함되어야 하는 모델을 정의한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stages&lt;/code&gt; 를 통해 두 버전 사이에 변경 사항을 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Migration Stage&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lightweight
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 프로퍼티에 &lt;code&gt;originalName&lt;/code&gt; 을 추가하거나 관계에서 삭제규칙을 지정하는 등의 가벼운 변경에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Custom
&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;code&gt;ModelContainer&lt;/code&gt; 의 &lt;code&gt;migrationPlan&lt;/code&gt; 매개변수에 마이그레이션 작업을 설정하면 완료된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let container = try? ModelContainer(for: Trip.self, migrationPlan: SampleMigrationPlan.self)&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;참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10195&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740445871945&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;Model your schema with SwiftData - WWDC23 - Videos - Apple Developer&quot; data-og-description=&quot;Learn how to use schema macros and migration plans with SwiftData to build more complex features for your app. We'll show you how to...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0nE6R/hyYjrY9kee/V6GoNZGhN5LIampY99av40/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0nE6R/hyYjrY9kee/V6GoNZGhN5LIampY99av40/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&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;Model your schema with SwiftData - WWDC23 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to use schema macros and migration plans with SwiftData to build more complex features for your app. We'll show you how to...&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;</description>
      <category>iOS/WWDC</category>
      <category>ios</category>
      <category>Swift</category>
      <category>swiftdata</category>
      <category>wwdc23</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/106</guid>
      <comments>https://iwntberich.tistory.com/106#entry106comment</comments>
      <pubDate>Tue, 25 Feb 2025 10:12:29 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/WWDC23] SwiftData에 대해 알아보자</title>
      <link>https://iwntberich.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftData는 데이터모델링과 관리 프레임워크이다.&lt;br /&gt;다른 포멧은 제외하고 코드에만 집중하며 Swift 매크로를 통해 매끄러운 API를 만든다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Schema 구성요소&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Model&lt;/h3&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;li&gt;값 프로퍼티가 속성으로 사용되도록 적응시킨다.&lt;/li&gt;
&lt;li&gt;기본 타입과 Struct, Enum Codable등 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Attribute&lt;/code&gt;를 통해 제약조건 추가 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Relationship&lt;/code&gt;으로 삭제 규칙 지정 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Transient&lt;/code&gt;로 특정 프로퍼티를 포함하지 않도록 할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;import SwiftData

@Model
class Trip {
    var name: String
    var destination: String
    var startDate: Date
    var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Attribute&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ .unique&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 키를 지정하기 위해서는 &lt;code&gt;Attribute&lt;/code&gt;의 &lt;code&gt;.unique&lt;/code&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;✔️ originalName&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@Model
class Trip {
    @Attribute(.unique) var name: String
    var destination: String
    var start_Date: Date
    var end_Date: Date
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 스키마에서 &lt;code&gt;start_Date&lt;/code&gt; , &lt;code&gt;end_Date&lt;/code&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@Model
class Trip {
    @Attribute(.unique) var name: String
    var destination: String
    @Attribute(originalName: &quot;start_Date&quot;) var startDate: Date
    @Attribute(originalName: &quot;end_Date&quot;) var endDate: Date
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 이름을 매핑하여 데이터 손실을 막을 수 있고, 앱 업데이트 시 간단한 마이그레이션을 보장하게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Relationship&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 간의 관계를 설정하고, &lt;code&gt;deleteRule&lt;/code&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;.cascade
&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;/li&gt;
&lt;li&gt;.nullify
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 객체가 삭제되면, 관계만 해제하고 자식 객체는 Nil이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;.deny
&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;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@Relationship(deleteRule: .cascade)
var bucketList: [BucketListItem]? = []

@Relationship(deleteRule.cascade)
var livingAccommodation: LivingAccommodation?&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Transient&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Transient&lt;/code&gt; 로 지정된 프로퍼티는 데이터를 SwiftData에 저장하지 않고, &lt;b&gt;런타임 동안에만 유지&lt;/b&gt;된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@Transient
var tripView: Int = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 기본 값이 지정되어 있어야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ModelContainer&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 저장소 관리&lt;/p&gt;
&lt;/blockquote&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;li&gt;데이터가 메모리 또는 디스크에 저장되도록 &lt;b&gt;저장 방식 관리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;버전 관리, 마이그레이션, 그래프 분리 같은 스토리지 운영 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let container = try ModelContainer(
    for: [ Trip.self, LivingAccommodation.self ],
    configuration: ModelConfiguration(url: URL(&quot;path&quot;))
)&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;SwiftUI에서 &lt;code&gt;.modelContainer(for: )&lt;/code&gt; modifier를 사용하여 컨테이너를 쉽게 주입할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WindowGroup&lt;/code&gt;에 &lt;code&gt;ModelContainer&lt;/code&gt;를 설정하면 그 안의 모든 뷰가 같은 컨테이너를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ModelConfiguration&lt;/h3&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;일시적인 데이터는 메모리에, 영구적인 데이터는 디스크에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 파일의 URL을 사용하거나, 그룹 컨테이너 권한 등 응용프로그램의 권한을 통해 URL 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터를 읽기 전용 모드&lt;/b&gt;로 로드하여 민감한 데이터나 템플릿 데이터의 수정을 방지&lt;/li&gt;
&lt;li&gt;두 개 이상의 CloudKit 컨테이너를 사용하는 응용프로그램은 &lt;code&gt;ModelConfiguration&lt;/code&gt;의 일부로 지정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let fullSchema = Schema([
    Trip.self,
    BucketListItem.self,
    LivingAccommodations.self,
    Person.self,
    Address.self
])

let trips = ModelConfiguration(
    schema: Schema([
        Trip.self,
        BucketListItem.self,
        LivingAccommodations.self
    ]),
    url: URL(filePath: &quot;/path/to/trip.store&quot;),
        cloudKitDatabase: .private(&quot;com.example.trips&quot;)
)

let people = ModelConfiguration(
    schema: Schema([Person.self, Address.self]),
    url: URL(filePath: &quot;/path/to/people.store&quot;),
    cloudKitDatabase: .private(&quot;com.example.people&quot;)
) 

let container = try? ModelContainer(
    for: fullSchema, 
    configurations: trips, people
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fullSchema&lt;/code&gt; 를 통해 프로젝트 내에서 사용하기 위한 스키마를 정의한다.&lt;br /&gt;&lt;code&gt;ModelConfiguration&lt;/code&gt; 을 통해 서로 다른 저장 위치에서 스키마를 불러온다.&lt;br /&gt;최종적으로 스키마와 &lt;code&gt;configuration&lt;/code&gt;을 결합하여 &lt;code&gt;ModelContainer&lt;/code&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;code&gt;ModelContainer&lt;/code&gt;는 &lt;code&gt;modifier&lt;/code&gt;를 통해 SwiftUI 뷰에 주입할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var body: some Scene {
    WindowGroup {
        ContentView()
    }
    .modelContainer(container)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ModelContext&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경을 처리하는 컨텍스트&lt;/p&gt;
&lt;/blockquote&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;b&gt;데이터를 저장하거나 삭제하는 기능을 제공&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;컨테이너가 설정되면 &lt;code&gt;ModelContext&lt;/code&gt;로 데이터를 가져와 저장할 준비를 할 수 있다.&lt;/li&gt;
&lt;li&gt;rollback과 reset기능을 지원하여 캐싱된 상태를 지울 수 있다.&lt;/li&gt;
&lt;li&gt;자동 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context 주입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View나 Scene에서 &lt;code&gt;.modelContainer&lt;/code&gt; 를 통해 주입하면, 컨테이너의 &lt;b&gt;mainContext 에 modelContext 키를 바인딩&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;MainContext는 특수한 MainActor 정렬 모델 컨텍스트로, Scene이나 View에서 ModelObject와 함께 작동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct ContextView: View {
    @Environment(\.modelContext) private var context
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context 접근&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Environment(\.modelContext)&lt;/code&gt; 를 사용하여 SwiftUI View 코드에서 모델의 쿼리에 쓰인 컨텍스트에 쉽게 접근할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;@Environment(\.modelContext)&lt;/code&gt; 는 기존 데이터의 변경사항을 추적할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct ContentView: View {
    @Query var trips: [Trip]
    @Environment(\.modelContext) private var context
    var body: some View {
        ...
        Button {
            modelContext.delete(trip)
        } label: {
            Label(&quot;Delete&quot;, systemImage: &quot;trash&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델에 변경사항이 생기면 스냅샷으로 &lt;code&gt;ModelContext&lt;/code&gt;에 기록된다. 계속해서 변경사항을 추적하다가 &lt;code&gt;context.save()&lt;/code&gt; 를 호출하면 멈춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 즉, 데이터를 삭제하더라도 &lt;code&gt;context.save()&lt;/code&gt; 를 하기 전 까지는 &lt;code&gt;ModelContext&lt;/code&gt;에 남아있게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Query&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@State&lt;/code&gt;와 비슷하게 동작하여, &lt;b&gt;모델에서 일어나는 모든 변화를 뷰에 업데이트&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Query&lt;/code&gt;를 사용하여 정렬, 필터와 같은 간단한 구문을 처리한 후의 데이터를 받아올 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@Query(sort: \.created) private var cards: [Card]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Undo/Redo&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.modelContainer()&lt;/code&gt; 는 &lt;code&gt;isUndoEnable&lt;/code&gt; 이라는 매개변수가 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;.modelContainer(for: Trip.self, isUndoEnabled: true)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;true&lt;/code&gt; 값을 주면 MainContext에 &lt;code&gt;undoManager&lt;/code&gt;를 바인딩하여, 추가 코드 없이 변경사항을 취소 또는 복귀를 수행할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자동 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동저장 기능이 활성화되면 ModelContext는 &lt;b&gt;시스템 이벤트(백그라운드 전환 또는 특정 주기) 발생 시 저장&lt;/b&gt;된다. 자동저장은 기본으로 활성화되어있어, 불필요 시 비활성화 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;.modelContainer(for: Trip.self, isAutosaveEnabled: false)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Model at scale&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백그라운드에서 데이터를 다루거나 원격 서버, 다른 영구 메커니즘과의 동기화, 배치 프로세스는 모두 모델 객체를 필요로한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 작업에서 필요한 객체 집합을 가져올 때 ModelContext의 &lt;code&gt;.fetch()&lt;/code&gt; 메서드를 통해 가져온다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FetchDescriptor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FetchDescriptor&lt;/code&gt;를 사용하여 복잡한 쿼리를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;#Predicate&lt;/code&gt; 매크로를 스키마와 결합하여 컴파일러가 검증한 쿼리를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 offset, limt, faulting, perfetching 옵션을 사용할 수 있다.&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;✔️ #Predicate&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 조건을 기반으로 데이터를 필터링 할 때 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var predicate = #Predicate&amp;lt;Trip&amp;gt; { trip in
    trip.livingAccommodations.filter {
        hotelNames.contains($0.placeName)
    }.count &amp;gt; 0
}
var descriptor = FetchDescriptor(predicate: predicate)
var trips = try context.fetch(descriptor)&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;✔️ SortDescriptor&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FetchDescriptor&lt;/code&gt; 와 함께 사용하여 데이터를 가져올 때 정렬을 수행한 후 값을 가져오도록 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let descriptor = FetchDescriptor&amp;lt;Trip&amp;gt;(
    sortBy: SortDescriptor(\\Trip.name),
    perdicate: tripPredicate
    )
let trips = try context.fetch(descriptor)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tuning Option&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ModelContext의 &lt;code&gt;enumerate&lt;/code&gt;함수에서 옵션들이 결합된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;context.enumerate(FetchDescriptor&amp;lt;Trip&amp;gt;()) { trip in

}&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;✔️ batchSize 조절&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;enumerate&lt;/code&gt;함수를 통해 배치사이즈 크기를 조절할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;context.enumerate(
    descriptor,
    batchSize: 1000) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치 크기를 줄이면 메모리를 적게 사용하게 되지만 I/O가 증가한다.&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;✔️ mutation guard&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 순회하는 동안 데이터가 변경됨을 감지하는 mutation guard를 내부적으로 포함하고 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;context.enumerate(descriptor) { trip in
    trip.name = &quot;새로운 이름&quot; // ❌ 예외 발생 가능!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 &lt;code&gt;ModelContext&lt;/code&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;code&gt;allowEscapingMutations&lt;/code&gt; 매개변수를 &lt;code&gt;true&lt;/code&gt;로 설정하여 &lt;b&gt;데이터의 변경을 허용&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;context.enumerate(
    descriptor,
    batchSize: 500,
    allowEscapingMutations: true
) { trip in
    // Remind me to make reservations for trip
}&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;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10187&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10187&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740035203211&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;Meet SwiftData - WWDC23 - Videos - Apple Developer&quot; data-og-description=&quot;SwiftData is a powerful and expressive persistence framework built for Swift. We'll show you how you can model your data directly from...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10187&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10187&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LdLFb/hyYjGuhNzt/e0KESKb0Dm5DbKduFNpqzk/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10187&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10187&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LdLFb/hyYjGuhNzt/e0KESKb0Dm5DbKduFNpqzk/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&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;Meet SwiftData - WWDC23 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SwiftData is a powerful and expressive persistence framework built for Swift. We'll show you how you can model your data directly from...&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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10195&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740035186162&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;Model your schema with SwiftData - WWDC23 - Videos - Apple Developer&quot; data-og-description=&quot;Learn how to use schema macros and migration plans with SwiftData to build more complex features for your app. We'll show you how to...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/G3x4R/hyYjHmrwmS/6KiEAUSxiI8vd2JdBp5X4K/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10195&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/G3x4R/hyYjHmrwmS/6KiEAUSxiI8vd2JdBp5X4K/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&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;Model your schema with SwiftData - WWDC23 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to use schema macros and migration plans with SwiftData to build more complex features for your app. We'll show you how to...&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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10196&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10196&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740035187501&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;Dive deeper into SwiftData - WWDC23 - Videos - Apple Developer&quot; data-og-description=&quot;Learn how you can harness the power of SwiftData in your app. Find out how ModelContext and ModelContainer work together to persist your...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10196&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10196&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FT2g9/hyYjkdKjC8/kHn3LvphPrnTpRLDeKDJ40/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10196&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2023/10196&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FT2g9/hyYjkdKjC8/kHn3LvphPrnTpRLDeKDJ40/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&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;Dive deeper into SwiftData - WWDC23 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how you can harness the power of SwiftData in your app. Find out how ModelContext and ModelContainer work together to persist your...&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;</description>
      <category>iOS/WWDC</category>
      <category>Database</category>
      <category>ios</category>
      <category>Swift</category>
      <category>swiftdata</category>
      <category>WWDC</category>
      <category>wwdc23</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/105</guid>
      <comments>https://iwntberich.tistory.com/105#entry105comment</comments>
      <pubDate>Thu, 20 Feb 2025 16:08:51 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/PointFree] 동시성프로그래밍: 구조적 프로그래밍과 MainActor</title>
      <link>https://iwntberich.tistory.com/104</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Structured programming&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조적 프로그래밍: 현대에 일반적으로 사용하고 있는 방식으로, 프로그램이 위에서 아래로 선형적으로 읽히도록 만드는 것을 목표로 하는 패러다임 프로그램의 일부를 블랙박스로 구성하여 항상 모든 세부사항을 알 필요 없게 된다. 기본 도구 - 조건문, 반복문, 함수 호출 및 재귀 등&lt;/p&gt;
&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;예시로 Swift에는 &lt;code&gt;for&lt;/code&gt;문이 없고, Jump문이 있다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 0부터 100 사이 모든 짝수를 출력하려면 아래와 같이 작성해볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var x = 0
top: if x.isMultiple(of: 2) {
  print(x)
}
x += 1
if x &amp;lt;= 100 {
  continue top
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 줄에 &lt;code&gt;top:&lt;/code&gt; 과 같은 레이블을 붙이고, 실행 흐름을 해당 레이블로 이동시킬 수 있는 &lt;code&gt;continue&lt;/code&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;&lt;b&gt;✔️ 점프문의 단점: 가독성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 점프문은 가독성 측면에서 &lt;code&gt;for&lt;/code&gt;문에 비해 좋지 않은 것을 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var x = 0
outer: var y = 0
inner: if x.isMultiple(of: 2) &amp;amp;&amp;amp; y.isMultiple(of: 2) {
  print(x, y)
}
y += 1
if y &amp;lt;= 100 {
  continue inner
}
x += 1
if x &amp;lt;= 100 {
  continue outer
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 중첩 반복을 for문을 사용한다면,&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for x in 0...100 {
  for y in 0...100 {
    if x.isMultiple(of: 2) &amp;amp;&amp;amp; y.isMultiple(of: 2) {
      print(x, y)
    }
  }
}&lt;/code&gt;&lt;/pre&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;&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;우리가 주로 사용하는 함수 호출과 동작 방식을 비교해보자면,&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func add(_ lhs: Int, _ rhs: Int) -&amp;gt; Int {
  lhs + rhs
}

add(3, 4)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 호출하면 해당 줄로 실행 이동하는 것 처럼 보이겠지만, &lt;code&gt;add&lt;/code&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;하지만, 점프문은 위와 같은 기능이 없다.&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;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;예를들어 &lt;code&gt;defer&lt;/code&gt;문을 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 &lt;code&gt;defer&lt;/code&gt; 문을 통해 함수 스코프의 끝에서 일부 로직을 실행하도록 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;defer { print(&quot;Outer loop finished&quot;) }
for x in 0...100 {
  defer { print(&quot;Inner loop finished for&quot;, x) }
  for y in 0...100 {
    if x.isMultiple(of: 2) &amp;amp;&amp;amp; y.isMultiple(of: 2) {
      print(x, y)
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드처럼 &lt;code&gt;defer&lt;/code&gt;를 통해 내부, 외부 for문이 종료되었음을 출력할 수 있다.&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;code&gt;defer&lt;/code&gt;문이 어느 스코프에 속하는지 알 수 없기 때문에 &lt;code&gt;defer&lt;/code&gt;문을 실행할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 코드를 사용하려면 조건문 뒤에 &lt;code&gt;print&lt;/code&gt;문을 삽입하는 수밖에 없다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var x = 0
outer: var y = 0
inner: if x.isMultiple(of: 2) &amp;amp;&amp;amp; y.isMultiple(of: 2) {
  print(x, y)
}
y += 1
if y &amp;lt;= 100 {
  continue inner
}
print(&quot;Inner loop finished&quot;, x)
x += 1
if x &amp;lt;= 100 {
  continue outer
}
print(&quot;Outer loop finished&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;code&gt;lock&lt;/code&gt;을 이용하여 내부를 잠근 후 &lt;code&gt;count&lt;/code&gt; 변수를 증가시키는 작업을 할때, 점프 문을 사용하게 되면 다른 코드로 점프한 후 돌아오지 않을 가능성이 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func increment() {
  self.lock.lock()
  self.count += 1
  continue somewhereElse
  self.lock.unlock()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 경우 잠금이 해제되지 않아 &lt;code&gt;increment()&lt;/code&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ 구조적 프로그래밍 툴을 벗어난 Swift도구&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;print(&quot;Before&quot;)
Thread.detachNewThread {
  print(Thread.current)
}
print(&quot;After&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 흐름 앞 뒤에 &lt;code&gt;print&lt;/code&gt;문을 추가하면&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Before
After
&amp;lt;NSThread: 0x10071c760&amp;gt;{number = 2, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 &amp;ldquo;After&amp;rdquo;가 출력 된 이후에 스레드 정보가 출력되는 것을 볼 수 있다.&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에서 익숙하게 사용하던 방식들이 깨지게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func thread() {
    defer { print(&quot;Finished&quot;) }
    print(&quot;Before&quot;)
    Thread.detachNewThread {
        print(Thread.current)
    }
    print(&quot;After&quot;)
}
thread()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Before
After
Finished
&amp;lt;NSThread: 0x100710d70&amp;gt;{number = 2, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드를 생성하기 전에 &lt;code&gt;defer&lt;/code&gt;문을 추가하였을 때 스레드의 종료보다 &lt;code&gt;defer&lt;/code&gt;문이 더 먼저 실행되는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드가 완전히 새로운 실행 흐름에서 동작하기 때문에 defer문은 스레드 작업이 끝나기를 기다릴 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 뿐만아니라 OperationQueue나 GCD 등에서도 마찬가지인데, 이는 스레드나 큐의 문제가 아니라 &lt;b&gt;Escaping Closure&lt;/b&gt;와 관련된 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Escaping Closure는 &lt;b&gt;함수의 생명주기를 넘어 캡쳐되고 사용될 수 있는 클로저&lt;/b&gt;이다. Escaping Closure를 사용하게 되면 실행 흐름과 완전히 분리된 새로운 흐름을 만들어낼 수 있게 된다.&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;Structured Concurrency&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;code&gt;defer&lt;/code&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;&lt;code&gt;requestId&lt;/code&gt;와 요청 시작 날짜 값을 &lt;code&gt;TaskLocal&lt;/code&gt; 변수로 설정하여 작업 시 값에 접근하도록 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;enum RequestData {
  @TaskLocal static var requestId: UUID!
  @TaskLocal static var startDate: Date!
}&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;code&gt;requestId&lt;/code&gt;를 사용해 로그를 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func databaseQuery() async throws {
  let requestId = RequestData.requestId!
  print(requestId, &quot;Making database query&quot;)
  try await Task.sleep(nanoseconds: 500_000_000)
  print(requestId, &quot;Finished database query&quot;)
}

func networkRequest() async throws {
  let requestId = RequestData.requestId!
  print(requestId, &quot;Making network request&quot;)
  try await Task.sleep(nanoseconds: 500_000_000)
  print(requestId, &quot;Finished network request&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;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func response(_ request: URLRequest) async throws -&amp;gt; HTTPURLResponse {
  let requestId = RequestData.requestId!
  let start = RequestData.startDate!
  defer { print(requestId, &quot;Request finished in&quot;, Date().timeIntervalSince(start)) }

  try await databaseQuery()
  try await networkRequest()

  return .init()
}&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 사용한 방식으로 두 비동기 함수를 병렬화 하기 위해 Task 를 사용하여 각각 새로운 작업을 시작하도록 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Task {
  try await databaseQuery()
}
Task {
  try await networkRequest()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식은 병렬적으로 작업을 수행하지만, 구조적 프로그래밍의 요소를 잃게 된다. Task를 사용하여 작업을 시작하게 되면 작업이 블로킹되지 않아 독립적으로 실행되므로 코드가 순차적으로 읽히지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 살펴본 &lt;code&gt;Escaping closure&lt;/code&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;p data-ke-size=&quot;size16&quot;&gt;두 작업이 병렬 작업으로 처리되면서 구조적 프로그래밍을 유지할 수 있는 방식에 대해 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task 타입은 Task가 반환한 값에 접근할 수 있는 프로퍼티를 제공한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let task = Task {
  try await Task.sleep(nanoseconds: NSEC_PER_SEC)
  return 42
}
let number = try await task.value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;task.value&lt;/code&gt; 를 통해 반환되는 값에 접근할 수 있으며 &lt;code&gt;await&lt;/code&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let databaseTask = Task {
  try await databaseQuery()
}
let networkTask = Task {
  try await networkRequest()
}
try await databaseTask.value
try await networkTask.value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 작업을 변수에 할당하고, 반환하는 값을 &lt;code&gt;await&lt;/code&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;실행해보면 약 0.5초의 시간만에 작업이 완료된다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;B2A20E2F-6389-4A0A-9BB0-9F6C2AB1FF86 Making database query
B2A20E2F-6389-4A0A-9BB0-9F6C2AB1FF86 Making network request
B2A20E2F-6389-4A0A-9BB0-9F6C2AB1FF86 Finished database query
B2A20E2F-6389-4A0A-9BB0-9F6C2AB1FF86 Finished network request
B2A20E2F-6389-4A0A-9BB0-9F6C2AB1FF86 Request finished in 0.552590012550354&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;p data-ke-size=&quot;size16&quot;&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;새로운 작업을 생성하게 되면 협력적인 취소를 수행하지 못하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 위의 응답 작업을 시작하고 0.1초만에 취소한다고 해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt3ffk/btsLVfo87MK/elQSGuDst7Vu8LzaEJDAa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt3ffk/btsLVfo87MK/elQSGuDst7Vu8LzaEJDAa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt3ffk/btsLVfo87MK/elQSGuDst7Vu8LzaEJDAa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt3ffk%2FbtsLVfo87MK%2FelQSGuDst7Vu8LzaEJDAa0%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;621&quot; height=&quot;327&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&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;내부에서 새로운 &lt;code&gt;Task&lt;/code&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;위와같이 동작하는 이유는 Apple의 구조적 동시성 설계 방식 때문이다. 작업을 생성하여 비구조적인 방식으로 동작하게 되면 해당 작업의 취소는 명시적으로 관리해야한다.&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;withTaskCancellationHandler&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;withTaskCancellationHandler&lt;/b&gt; 를 사용하여 비구조적 코드를 구조적 코드로 되돌리고, 취소도 우리가 원하는 대로 동작하도록 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;withTaskCancellationHandler(
    operation: () async throws -&amp;gt; T,
    onCancel: () -&amp;gt; Void
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;operation&lt;/code&gt; 에서 실행할 비동기 작업을 처리하고, 취소가 감지되었을 때 &lt;code&gt;onCancel&lt;/code&gt; 에서 취소 작업을 수행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cezTx4/btsLVilRokS/9DaogDjimsdxLvrdT5Xkg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cezTx4/btsLVilRokS/9DaogDjimsdxLvrdT5Xkg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cezTx4/btsLVilRokS/9DaogDjimsdxLvrdT5Xkg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcezTx4%2FbtsLVilRokS%2F9DaogDjimsdxLvrdT5Xkg1%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;648&quot; height=&quot;303&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;withTaskCancellationHandler&lt;/code&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;code&gt;withTaskCancellationHandler&lt;/code&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;✔️ async let&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 간단하게 &lt;code&gt;async let&lt;/code&gt; 을 사용해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async let 을 통해 여러 비동기 작업을 병렬로 실행하면서 코드가 선형적으로 읽히며 취소 작업도 전달할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;async let databaseResponse = databaseQuery()
async let networkResponse = networkRequest()&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct User { var id: Int }
func fetchUser() async throws -&amp;gt; User { User(id: 42) }

struct StripeSubscription { var id: Int }
func fetchSubscription() async throws -&amp;gt; StripeSubscription { StripeSubscription(id: 1729) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시에서 &lt;code&gt;async let&lt;/code&gt; 을 통해 병렬로 데이터를 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;async let user = fetchUser()
async let subscription = fetchSubscription()&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;code&gt;async let&lt;/code&gt; 을 통해 가져온 변수는 &lt;code&gt;await&lt;/code&gt; 키워드를 사용하여 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;try await user.id&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;이 병렬 작업 결과를 JSON으로 묶어 응답을 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct Response: Encodable {
  let user: User
  let subscription: StripeSubscription
}

try await JSONEncoder().encode(Response(user: user, subscription: subscription))

async let user = fetchUser()
async let subscription = fetchSubscription()

let jsonData = try await JSONEncoder().encode(
  Response(user: user, subscription: subscription)
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;async let&lt;/code&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;✔️ task group&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async let 은 병렬 작업의 개수가 정해져있는 경우에 효과적으로 사용할 수 있지만, 작업 단위의 개수가 동적으로 정해지는 경우에는 다른 도구가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;withTaskGroup&lt;/b&gt; 을 통해 동적인 개수의 작업을 수행하는 동안 실행을 중지하고, 모든 작업이 완료된 후 다시 실행을 하도록 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;withTaskGroup(
  of: Sendable,
  returning: GroupResult.Type,
  body: (inout TaskGroup&amp;lt;Sendable&amp;gt;) async -&amp;gt; GroupResult
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 매개변수 &lt;code&gt;of&lt;/code&gt;는 각 하위 task의 반환 타입을 의미한다.&lt;br /&gt;두번째 매개변수인 &lt;code&gt;returning&lt;/code&gt; 은 &lt;code&gt;task group&lt;/code&gt;의 최종 반환 타입을 의미하고,&lt;br /&gt;&lt;code&gt;body&lt;/code&gt; 는 &lt;code&gt;task group&lt;/code&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;예를들어, 1000개의 작업을 생성해 숫자를 모두 더하는 작업을 수행해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 스레드를 기반으로 작업을 해보면&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var sum = 0
for n in 1...1_000 {
  Thread.detachNewThread {
    Thread.sleep(forTimeInterval: 1)
    sum += n
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000개의 새로운 스레드를 생성한 후 작업을 처리하는데, 스레드를 사용하면 모든 스레드가 종료될 때 까지 기다려야 결과를 확인할 수 있다. 스레드 자체에 기다리는 작업이 없기 때문에 수동으로 대기 시간을 설정하여 기다려야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Thread.sleep(forTimeInterval: 2)
print(sum)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 수행하면 &lt;b&gt;race condition 문제가 발생&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;이 코드를 Task Group을 사용하는 방식으로 구현해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;withTaskGroup(of: Int.self, returning: Int.self) { group in
  group.addTask(operation: () async -&amp;gt; Int)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;of&lt;/code&gt;와 &lt;code&gt;returning&lt;/code&gt;에 Int 타입을 지정해주고, &lt;code&gt;body&lt;/code&gt;에 task를 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task group에 task를 추가할 때는 &lt;code&gt;addTask&lt;/code&gt; 메서드를 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가되는 &lt;code&gt;task&lt;/code&gt;는 매개변수가 없는 비동기 함수이며, &lt;code&gt;of&lt;/code&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;다음 코드는 1000개의 정수를 반복하며 1초동안 대기 후 반환하는 Task를 추가하는 코드이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Task {
  await withTaskGroup(of: Int.self) { group in
    for n in 1...1000 {
      group.addTask {
        try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
        return n
      }
    }
    await group.waitForAll()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Task는 병렬로 실행되고, &lt;code&gt;await group.waitForAll()&lt;/code&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;더 나아가서, 각 Task의 실행 결과를 수집할 수 있다. &lt;code&gt;for await&lt;/code&gt; 을 이용하여 그룹 내에 있는 각 Task의 실행 결과에 접근할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var sum = 0
for await int in group {
  sum += int
}
return sum&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task group은 &lt;b&gt;AsyncSequence&lt;/b&gt; 프로토콜을 준수한다. Sequence와 유사하지만 AsyncSequence는 &lt;code&gt;next()&lt;/code&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부터 1000까지의 숫자를 병렬적으로 계산하여 빠르게 수행하도록 하면&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let sum = await withTaskGroup(of: Int.self, returning: Int.self) { group in
  for n in 1...1_000 {
    group.addTask {
      try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
      return n
    }
  }

  var sum = 0
  for await int in group {
    sum += int
  }
  return sum
}
print(sum)&lt;/code&gt;&lt;/pre&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;&lt;b&gt;TaskGroup&lt;/b&gt;을 사용하여 &lt;b&gt;Race Condition 문제를 해결&lt;/b&gt;할 수 있으며, 공유되는 가변 데이터에 동시적인 접근을 막기 위해 &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;✔️ TaskGroup의 작업 취소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TaskGroup은 협력적 취소를 지원한다. TaskGroup을 시작한 비동기 컨텍스트가 취소되었을 때 &lt;code&gt;isCancelled&lt;/code&gt; 플래그를 통해 작업을 마무리하도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;addTaskUnlessCancelled&lt;/code&gt; 를 통해 상위 Task의 취소 여부를 확인하여 취소되었을 경우 Task를 추가하지 않도록 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;group.addTaskUnlessCancelled {
  // 작업 추가
}&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;✔️ TaskGroup의 예외처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TaskGroup에서 &lt;code&gt;withThrowingTaskGroup&lt;/code&gt; 을 통해 비동기 작업에서 예외가 발생하거나 취소될 경우를 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let sum = await withThrowingTaskGroup(of: Int.self, returning: Int.self) { group in
  for n in 1...1_000 {
    group.addTask {
      try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
      return n
    }
  }

  var sum = 0
  for try await int in group {
    sum += int
  }
  return sum
}
print(sum)&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;TaskGroup&lt;/b&gt;을 사용하여 여러 개의 Task를 병렬로 실행할 수 있고, 이러한 Task의 실행 결과를 모아 최종 결과로 누적하여 사용할 수 있다. 이러한 과정을 처리하면서도 &lt;b&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;/b&gt;. &lt;code&gt;async&lt;/code&gt;함수와 &lt;code&gt;await&lt;/code&gt;문, &lt;code&gt;async let&lt;/code&gt;, &lt;code&gt;task group&lt;/code&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;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 비동기 작업을 처리해야하는데 현재 비동기 컨텍스트에 있지 않다면 어떻게 해야할까. 새로운 Task를 생성하여 비동기 컨텍스트를 만들 수도 있지만, 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;Swift는 계속해서 이러한 비구조적인 Task를 생성해야 하는 상황을 해결하기 위한 API를 추가하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어, SwiftUI는 뷰가 나타날 때 비동기 Task를 실행하고, 뷰가 사라질 때 Task를 취소하는 &lt;b&gt;modifier&lt;/b&gt;를 제공한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;import SwiftUI

Text(&quot;Hi&quot;)
  .task {
    let sum = try? await withThrowingTaskGroup(
      of: Int.self, returning: Int.self
    ) { group in
      // ...
    }
    print(&quot;sum&quot;, sum)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.task&lt;/code&gt; 를 통해 비동기 작업을 실행할 수 있고, &lt;code&gt;.task&lt;/code&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에서 최상위 레벨에서는 비동기 작업을 수행할 수 없었다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;try await Task.sleep(nanoseconds: NSEC_PER_SEC)
// Error: &amp;lsquo;async&amp;rsquo; call in a function that does not support concurrency&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하려면 &lt;code&gt;@main&lt;/code&gt; 을 사용하여 Entry point를 설정해 비동기 컨텍스트를 실행할 수 있는 환경을 만들어야 했다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@main
struct Main {
  static func main() async throws {
    try await Task.sleep(nanoseconds: NSEC_PER_SEC)
    print(&quot;done!&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 5.7부터는 이러한 문제가 해결되어 최상위 코드에서 비동기 작업을 바로 사용할 수 있게 되었다.&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;Apple의 프레임워크가 Swift의 동시성 도구와 깊이 통합됨에 따라 비구조적 Task를 생성할 필요가 거의 없어질 것으로 보인다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Unstructured Concurrency&lt;/h2&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;b&gt;✔️ Task의 상속&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task는 아래와 같이 &lt;code&gt;TaskLocals&lt;/code&gt;와 우선순위를 상속받는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSPr8A/btsLTrLBthx/czPuNrtYQsKUXZGoPSHpt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSPr8A/btsLTrLBthx/czPuNrtYQsKUXZGoPSHpt1/img.png&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;756&quot; data-is-animation=&quot;false&quot; width=&quot;343&quot; height=&quot;377&quot; style=&quot;width: 308px; margin-right: 10px;&quot; data-widthpercent=&quot;42.97&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSPr8A/btsLTrLBthx/czPuNrtYQsKUXZGoPSHpt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSPr8A%2FbtsLTrLBthx%2FczPuNrtYQsKUXZGoPSHpt1%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;618&quot; height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3f1xn/btsLTqy5PTW/PTg0nV2Q5YxPUR5UZxzm40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3f1xn/btsLTqy5PTW/PTg0nV2Q5YxPUR5UZxzm40/img.png&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;554&quot; data-is-animation=&quot;false&quot; width=&quot;222&quot; height=&quot;396&quot; style=&quot;width: 430px;&quot; data-widthpercent=&quot;57.03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3f1xn/btsLTqy5PTW/PTg0nV2Q5YxPUR5UZxzm40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3f1xn%2FbtsLTqy5PTW%2FPTg0nV2Q5YxPUR5UZxzm40%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;601&quot; height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;또, Task는 &lt;code&gt;Actor&lt;/code&gt;를 상속받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;actor Counter {
  var count = 0

  func increment() {
    self.count += 1
  }

  func decrement() async throws {
    self.count -= 1
    try await Task.sleep(nanoseconds: NSEC_PER_SEC / 2)
    if self.count &amp;lt; 0 {
      self.count += 1
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 위와 같이 &lt;code&gt;decrement()&lt;/code&gt; 함수 내에서 &lt;code&gt;count&lt;/code&gt; 가 0보다 작아질 경우 증가시켜주는 작업을 하고싶을 때, &lt;code&gt;Task.sleep&lt;/code&gt; 을 이용하여 작업을 잠깐 중단하도록 하는 것은 바람직하지 않을 수 있다. 일단 값을 감소시키고 나중에 Actor가 자동으로 조정하도록 하고 싶을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우 비 구조적 Task를 사용해볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func decrement() {
  self.count -= 1
  Task {
    try await Task.sleep(nanoseconds: NSEC_PER_SEC / 2)
    if self.count &amp;lt; 0 {
      self.count += 1
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성한다면 원하는대로 동작하는데, 위 코드에서 놀라운 부분이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 Actor 메서드와 프로퍼티에 접근하려고 할 때는 Actor 외부에서는 &lt;code&gt;await&lt;/code&gt; 을 사용하여 접근해야 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6mrkQ/btsLTFXcmC4/ZNN4YO4ZnLCjey1JeVHgL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6mrkQ/btsLTFXcmC4/ZNN4YO4ZnLCjey1JeVHgL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6mrkQ/btsLTFXcmC4/ZNN4YO4ZnLCjey1JeVHgL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6mrkQ%2FbtsLTFXcmC4%2FZNN4YO4ZnLCjey1JeVHgL1%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;611&quot; height=&quot;120&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&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;code&gt;decrement()&lt;/code&gt;에서 Task는 Actor 내부에 존재하지만, Task는 Actor와 독립적인 흐름을 시작하게 된다. 그럼에도 불구하고 Actor 내부에 있는 Task 블록에서 await없이 프로퍼티와 메서드에 접근할 수 있는 이유는 &lt;b&gt;Task가 Actor의 컨텍스트를 상속&lt;/b&gt;받기 때문이다. 그렇기 때문에 Task 안에서도 Actor 내부에서 동작하는 것 처럼 접근할 수 있다.&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;&lt;b&gt;✔️ 분리된 Task에서의 Actor&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에, 현재 컨텍스트에서 &lt;b&gt;완전히 분리된 Task를 생성&lt;/b&gt;할 수 있다. 이는 우선순위, Task Local값, actor를 상속받지 않는다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Task.detached {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEIcMD/btsLSp8vPc0/kAW8zJdQCOvKp0yKmVXzXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEIcMD/btsLSp8vPc0/kAW8zJdQCOvKp0yKmVXzXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEIcMD/btsLSp8vPc0/kAW8zJdQCOvKp0yKmVXzXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEIcMD%2FbtsLSp8vPc0%2FkAW8zJdQCOvKp0yKmVXzXk%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;604&quot; height=&quot;194&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 &lt;code&gt;await&lt;/code&gt; 을 통해 접근해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func decrement() {
  self.count -= 1
  Task.detached {
    try await Task.sleep(nanoseconds: NSEC_PER_SEC / 2)
    if await self.count &amp;lt; 0 {
      await self.increment()
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분리된 Task는 완전히 초기화 된 상태에서 시작&lt;/b&gt;하게 된다. 뿐만아니라 구조적인 동시성을 위한 도구 중 일부도 현재 Task의 요소를 상속받지 않는 것이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Sendable&lt;/code&gt;, &lt;code&gt;async let&lt;/code&gt;, &lt;code&gt;Taskgroup&lt;/code&gt;은 현재 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;예를들어, 동기 클로저 내부에서 actor 프로퍼티에 접근하는 것은 문제가 발생하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func increment() {
  self.count += 1
  let count = { self.count }()
}&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;code&gt;@Sendable&lt;/code&gt; 을 적용하게 되면 컴파일 오류가 발생하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNIgxq/btsLUUFvZNy/Iwg58S6QvUNt0yqLziHSm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNIgxq/btsLUUFvZNy/Iwg58S6QvUNt0yqLziHSm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNIgxq/btsLUUFvZNy/Iwg58S6QvUNt0yqLziHSm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNIgxq%2FbtsLUUFvZNy%2FIwg58S6QvUNt0yqLziHSm1%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;623&quot; height=&quot;100&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하려면 비동기 컨텍스트를 제공하여 &lt;code&gt;await&lt;/code&gt; 을 사용하여 접근해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func increment() async {
  let count = await { @Sendable in await self.count }()
}&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가 어떻게 Actor 컨텍스트를 상속받을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task는 공개적으로 노출되지 않은 특수 컴파일러의 속성을 통해 actor 컨텍스트를 상속하게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;public init(
  priority: TaskPriority? = nil,
  @_inheritActorContext
  @_implicitSelfCapture
  operation: __owned @Sendable @escaping () async -&amp;gt; Success
) {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor 컨텍스트를 상속 받는 것은 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;code&gt;@Sendable&lt;/code&gt; 과 마찬가지로 &lt;code&gt;async let&lt;/code&gt; 또한 상속받지 못한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJCJyy/btsLTsXLbds/URcmgeBznARx3NpAtpis0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJCJyy/btsLTsXLbds/URcmgeBznARx3NpAtpis0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJCJyy/btsLTsXLbds/URcmgeBznARx3NpAtpis0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJCJyy%2FbtsLTsXLbds%2FURcmgeBznARx3NpAtpis0K%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;707&quot; height=&quot;74&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;async let&lt;/code&gt;은 비동기적으로 실행되는 작업을 동시에 실행하기 위해 사용된다. 다른 작업과 병렬적으로 실행되야 하기 때문에 클로저를 자동으로 &lt;code&gt;@Sendable&lt;/code&gt; 속성으로 변환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 &lt;code&gt;async let&lt;/code&gt;은 actor 격리를 유지하지 않게 되어 actor의 프로퍼티에 접근할 수 없게 된다. 이 오류를 해결하기 위해서는 &lt;code&gt;await&lt;/code&gt; 을 사용하여 접근하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func increment() async {
  async let count = { await self.count }()
}&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 group도 상속받지 못한다. 예시로 1000개의 작업을 생성하여 &lt;code&gt;count&lt;/code&gt;를 증가 또는 감소시키는 메서드를 구현해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rtwE0/btsLUimR3sI/LKcRzq3LUT9qqiEVYXGAi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rtwE0/btsLUimR3sI/LKcRzq3LUT9qqiEVYXGAi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rtwE0/btsLUimR3sI/LKcRzq3LUT9qqiEVYXGAi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrtwE0%2FbtsLUimR3sI%2FLKcRzq3LUT9qqiEVYXGAi1%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;665&quot; height=&quot;232&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류가 발생하는 이유는 addTask 클로저는 &lt;code&gt;@Sendable&lt;/code&gt; 클로저이기 때문에 현재 actor를 상속받지 못한다. 그렇기 때문에 비동기 컨텍스트를 사용하여 접근해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;group.addTask {
  if Bool.random() {
    await self.increment()
  } else {
    await self.decrement()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MainActor&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MainActor의 필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 동시성 도구를 사용할 때 스레드에 대해 고려할 필요가 거의 없다. 하지만 특정 작업을 수행할 때 메인 스레드에서 실행되도록 해야 할 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 살펴본 바와 같이 작업을 &lt;code&gt;Task.sleep&lt;/code&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;code&gt;count&lt;/code&gt; 변수를 가지고 있고, 복잡한 연산을 수행하는 비동기 메서드를 포함하고 있는 &lt;code&gt;ObservableObject&lt;/code&gt; 가 있다고 가정해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class ViewModel: ObservableObject {
  @Published var count = 0

  func perform() async throws {
    try await Task.sleep(nanoseconds: NSEC_PER_SEC)
    self.count = .random(in: 1...1_000)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 경고 없이 컴파일되지만 잘못된 코드이다. &lt;code&gt;self.count&lt;/code&gt; 를 변경하는 코드가 어떤 스레드에서 실행될 지 알 수 없다. 하지만 &lt;code&gt;count&lt;/code&gt; 는 &lt;code&gt;@Published&lt;/code&gt; 로 선언했고, &lt;b&gt;@Published는 반드시 메인 스레드에서 변경되어야 한다&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;code&gt;DispatchQueue.main.async&lt;/code&gt; 를 생각해볼 수 있을 것이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class ViewModel: ObservableObject {
  @Published var count = 0

  func perform() async throws {
    try await Task.sleep(nanoseconds: NSEC_PER_SEC)
    DispatchQueue.main.async {
      self.count = .random(in: 1...1_000)
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 오류가 발생하는데, ViewModel이 &lt;code&gt;Sendable&lt;/code&gt; 타입이 아니기 때문이다. 그렇기 때문에 &lt;code&gt;self&lt;/code&gt;를 캡쳐할 수 없다. ViewModel은 가변 데이터를 포함하고 있기 때문에 쉽게 &lt;code&gt;Sendable&lt;/code&gt; 로 만들 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게다가, DispatchQueue 의 &lt;code&gt;async&lt;/code&gt;는 전달되는 클로저가 &lt;code&gt;Sendable&lt;/code&gt; 이어야 한다고 요구하기 때문에 &lt;code&gt;Sendable&lt;/code&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의 기본 동시성 도구가 제공하는 장점들을 사용하지 못하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어, 메인 큐로 Dispatch하게되면서 &lt;code&gt;Task Locals&lt;/code&gt;를 상속할 수 없게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JC3VW/btsLSLKf4We/dMrovTvnO2XGRCvKKYK5E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JC3VW/btsLSLKf4We/dMrovTvnO2XGRCvKKYK5E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JC3VW/btsLSLKf4We/dMrovTvnO2XGRCvKKYK5E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJC3VW%2FbtsLSLKf4We%2FdMrovTvnO2XGRCvKKYK5E1%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;664&quot; height=&quot;213&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DispatchQueue.main.async&lt;/code&gt; 클로저 내에서 접근할 때 런타임 오류가 발생하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제가 발생하는 이유는 Task Local이 &lt;code&gt;withValue&lt;/code&gt; 클로저의 수명 동안만 접근할 수 있기 때문이다. 이 클로저는 non-escaping이고, 동기적으로 호출되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 명확하게 실행 범위를 확인해보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1W0W5/btsLS7GwKJJ/u07P4ugYSQrXaqTVUkPHS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1W0W5/btsLS7GwKJJ/u07P4ugYSQrXaqTVUkPHS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1W0W5/btsLS7GwKJJ/u07P4ugYSQrXaqTVUkPHS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1W0W5%2FbtsLS7GwKJJ%2Fu07P4ugYSQrXaqTVUkPHS0%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;567&quot; height=&quot;304&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;withvalue&lt;/code&gt; 가 종료된 이후에 &lt;code&gt;DispatchQueue.main.async&lt;/code&gt; 가 실행되는 것을 볼 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MainActor&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 메인 스레드에서 강제로 실행하기 위한 안전한 방법을 찾아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드가 메인 스레드라는 개념을 가지고 있고, DispatchQueue가 메인 큐를 가지고 있는 것 처럼, Actor에는 &lt;b&gt;MainActor&lt;/b&gt;라는 개념이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yWP0J/btsLUS8L5NJ/MJdXt1SGjY7HWOouv8LBj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yWP0J/btsLUS8L5NJ/MJdXt1SGjY7HWOouv8LBj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yWP0J/btsLUS8L5NJ/MJdXt1SGjY7HWOouv8LBj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyWP0J%2FbtsLUS8L5NJ%2FMJdXt1SGjY7HWOouv8LBj0%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;685&quot; height=&quot;246&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&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;MainActor&lt;/b&gt; 타입은 &lt;b&gt;메인 스레드에서 동기적으로 클로저를 실행할 수 있는 Endpoint를 제공&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;await MainActor.run {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainActor의 &lt;code&gt;run&lt;/code&gt; 메서드를 호출하려면 &lt;code&gt;await&lt;/code&gt;키워드를 사용해야 한다. 다른 작업이 이미 메인 스레드에서 동작중일 경우 시간이 걸릴 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHGHMg/btsLS8FswyE/HiqJ6ur511dld5uBqwz5CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHGHMg/btsLS8FswyE/HiqJ6ur511dld5uBqwz5CK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHGHMg/btsLS8FswyE/HiqJ6ur511dld5uBqwz5CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHGHMg%2FbtsLS8FswyE%2FHiqJ6ur511dld5uBqwz5CK%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;668&quot; height=&quot;226&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 &lt;code&gt;MainActor.run&lt;/code&gt;에서 count 를 변경하려 하면 오류가 발생한다. Non-Sendable 타입의 데이터를 수정해야 하기 때문에 &lt;b&gt;race condition이 발생할 가능성&lt;/b&gt;이 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;await MainActor.run {
  self.count = .random(in: 1...1_000)
}
await MainActor.run {
  self.count = .random(in: 1...1_000)
}&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;이러한 문제를 해결하기 위해 메서드에 MainActor 속성을 부여할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6XepP/btsLSLXOb2p/Gaykrm71ilaTagKUJl2Tu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6XepP/btsLSLXOb2p/Gaykrm71ilaTagKUJl2Tu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6XepP/btsLSLXOb2p/Gaykrm71ilaTagKUJl2Tu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6XepP%2FbtsLSLXOb2p%2FGaykrm71ilaTagKUJl2Tu1%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;513&quot; height=&quot;413&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드가 &lt;code&gt;@MainActor&lt;/code&gt; 로 표시되었다고 해서 모든 것이 메인스레드에서 동작하는 것은 아니다. 다른 비동기 메서드를 호출하거나 &lt;code&gt;Task.sleep&lt;/code&gt; 등 현재 작업의 일시 중단 지점에서는 다른 스레드에서 동작할 수 있다. &lt;code&gt;@MainActor&lt;/code&gt; 로 표시된 메서드 내에서도 비동기적이고 병렬적인 작업을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 메서드 내에서 &lt;code&gt;sleep&lt;/code&gt; 을 호출한다고 해서 메인 스레드를 blocking하지는 않는다. &lt;code&gt;sleep&lt;/code&gt; 이 실행되는 동안 다른 작업이 MainActor에서 실행될 수 있다.&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;메서드 뿐만아니라 클래스에도 &lt;code&gt;@MainActor&lt;/code&gt; 를 적용해 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@MainActor
class ViewModel: ObservableObject {
     ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스에 적용하면 모든 생성자, 메서드 프로퍼티가 모두 &lt;code&gt;@MainActor&lt;/code&gt; 로 표시된다. 또한, 클래스 전체를 &lt;code&gt;@Sendable&lt;/code&gt;로 만들며, &lt;b&gt;이 클래스가 병렬적인 컨텍스트에서도 안전하게 사용될 수 있음을 보장&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@MainActor&lt;/code&gt; 를 선언함으로써 &lt;b&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;Task를 사용하면 자동으로 Actor 컨텍스트를 상속&lt;/b&gt;받게 된다. 따라서 아래 코드는 메인스레드에서 실행된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@MainActor
func perform() async throws {
  Task {
    print(Thread.current)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;&amp;lt;_NSMainThread: 0x10600a9b0&amp;gt;{number = 1, name = main}&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.detached를 사용하면 Actor 컨텍스트를 상속받지 않으며&lt;/b&gt;, 백그라운드에서 실행된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@MainActor
func perform() async throws {
  Task.detached {
    print(Thread.current)
  }
}&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;code&gt;async let&lt;/code&gt; 이나 &lt;code&gt;Task Group&lt;/code&gt;과 같은 비동기 병렬 구조를 MainActor 코드에 도입하면 해당 작업은 메인스레드가 아닌 다른 스레드에서 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@MainActor&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;p data-ke-size=&quot;size16&quot;&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;이를 알아보기 위해 여러 작업을 실행하면서 모든 작업이 메인스레드에서 실행되도록 강제해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.25초마다 로그를 출력하는 작업을 수행하고,&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;await withThrowingTaskGroup(of: Void.self) { group in
  group.addTask { @MainActor in
    while true {
      try await Task.sleep(nanoseconds: NSEC_PER_SEC / 4)
      print(Thread.current, &quot;Timer ticked&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;동시에 2,000,000번째 소수를 계산하는 작업을 수행한다. 이 작업은 많은 시간을 소모하게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;group.addTask { @MainActor in
  nthPrime(2_000_000)
}&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;또, 동시에 대용량 파일 1,000개 다운로드 작업을 수행한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for n in 0..&amp;lt;workCount {
  group.addTask { @MainActor in
    _ = try await URLSession.shared
      .data(from: .init(string: &quot;http://ipv4.download.thinkbroadband.com/1MB.zip&quot;)!)
    print(Thread.current, &quot;Download finished&quot;, n)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oDUTj/btsLVlpmIhV/iznmVyMmS6QsrJ3iV22xc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oDUTj/btsLVlpmIhV/iznmVyMmS6QsrJ3iV22xc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oDUTj/btsLVlpmIhV/iznmVyMmS6QsrJ3iV22xc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoDUTj%2FbtsLVlpmIhV%2FiznmVyMmS6QsrJ3iV22xc1%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;559&quot; height=&quot;341&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 작업을 수행하면 초기에는 로그가 출력되지 않다가, 약 6초 후에 로그가 출력된다. 소수를 계산하는 함수가 매우 강력한 연산을 수행하게되며 메인스레드를 묶어버리기 때문이다.&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;그렇다면 소수 계산 함수를 비동기로 변경하고, 1000번마다 다른 작업이 실행될 수 있도록 양보(yield)해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1589&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FdNwj/btsLTRJK4f7/SVPfpVTTailhKk87YDBNq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FdNwj/btsLTRJK4f7/SVPfpVTTailhKk87YDBNq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FdNwj/btsLTRJK4f7/SVPfpVTTailhKk87YDBNq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFdNwj%2FbtsLTRJK4f7%2FSVPfpVTTailhKk87YDBNq0%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;556&quot; height=&quot;374&quot; data-origin-width=&quot;1589&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 즉시 로그가 출력되고, 여러 작업이 동시에 실행된다. 그리고 약 6초 후 소수 계산 결과가 출력된다.&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;b&gt;작업간 차단을 최소화&lt;/b&gt;하여 &lt;b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PointFree 강의를 듣고 정리한 내용입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/episodes/ep194-concurrency-s-future-structured-and-unstructured&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.pointfree.co/episodes/ep194-concurrency-s-future-structured-and-unstructured&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1737428044669&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;Episode #194: Concurrency's Future: Structured and Unstructured&quot; data-og-description=&quot;There are amazing features of Swift concurrency that don&amp;rsquo;t quite fit into our narrative of examining it through the lens of past concurrency tools. Instead, we&amp;rsquo;ll examine them through the lens of a past programming paradigm, structured programming, and&quot; data-og-host=&quot;www.pointfree.co&quot; data-og-source-url=&quot;https://www.pointfree.co/episodes/ep194-concurrency-s-future-structured-and-unstructured&quot; data-og-url=&quot;https://www.pointfree.co/episodes/ep194-concurrency-s-future-structured-and-unstructured&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHX6fn/hyX4uuMqch/c0hiBJu26ylVtIbkgP9hW1/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=261_270_1725_753,https://scrap.kakaocdn.net/dn/oKc3u/hyX4nWH7om/3zf30LjVkOhEPyUqzJbHKK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=261_270_1725_753&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/episodes/ep194-concurrency-s-future-structured-and-unstructured&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.pointfree.co/episodes/ep194-concurrency-s-future-structured-and-unstructured&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHX6fn/hyX4uuMqch/c0hiBJu26ylVtIbkgP9hW1/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=261_270_1725_753,https://scrap.kakaocdn.net/dn/oKc3u/hyX4nWH7om/3zf30LjVkOhEPyUqzJbHKK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=261_270_1725_753');&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;Episode #194: Concurrency's Future: Structured and Unstructured&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;There are amazing features of Swift concurrency that don&amp;rsquo;t quite fit into our narrative of examining it through the lens of past concurrency tools. Instead, we&amp;rsquo;ll examine them through the lens of a past programming paradigm, structured programming, and&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.pointfree.co&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;</description>
      <category>iOS/Swift</category>
      <category>actor</category>
      <category>concurrency</category>
      <category>ios</category>
      <category>mainactor</category>
      <category>pointfree</category>
      <category>Swift</category>
      <category>동시성프로그래밍</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/104</guid>
      <comments>https://iwntberich.tistory.com/104#entry104comment</comments>
      <pubDate>Tue, 21 Jan 2025 12:13:45 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/Point-Free] 동시성프로그래밍: Sendable, Actor</title>
      <link>https://iwntberich.tistory.com/103</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;PointFree 강의 정리 내용입니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/collections/concurrency/threads-queues-and-tasks/ep193-concurrency-s-future-sendable-and-actors&quot;&gt;https://www.pointfree.co/collections/concurrency/threads-queues-and-tasks/ep193-concurrency-s-future-sendable-and-actors&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1735535683894&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;Episode #193: Concurrency's Future: Sendable and Actors&quot; data-og-description=&quot;When working with concurrent code, you must contend with data synchronization and data races. While the tools of the past made it difficult to reason about these issues, Swift&amp;rsquo;s new tools make it a breeze, including the Sendable protocol, @Sendable closu&quot; data-og-host=&quot;www.pointfree.co&quot; data-og-source-url=&quot;https://www.pointfree.co/collections/concurrency/threads-queues-and-tasks/ep193-concurrency-s-future-sendable-and-actors&quot; data-og-url=&quot;https://www.pointfree.co/collections/concurrency/threads-queues-and-tasks/ep193-concurrency-s-future-sendable-and-actors&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bh2dSy/hyXSBaxSsG/ZRVoB9R4RtnkCMoCsxb4JK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=411_306_1608_744,https://scrap.kakaocdn.net/dn/lN1nY/hyXSCHiCdA/hCQJaChgWu3Py8RXXsssKk/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=411_306_1608_744&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/collections/concurrency/threads-queues-and-tasks/ep193-concurrency-s-future-sendable-and-actors&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.pointfree.co/collections/concurrency/threads-queues-and-tasks/ep193-concurrency-s-future-sendable-and-actors&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bh2dSy/hyXSBaxSsG/ZRVoB9R4RtnkCMoCsxb4JK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=411_306_1608_744,https://scrap.kakaocdn.net/dn/lN1nY/hyXSCHiCdA/hCQJaChgWu3Py8RXXsssKk/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=411_306_1608_744');&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;Episode #193: Concurrency's Future: Sendable and Actors&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;When working with concurrent code, you must contend with data synchronization and data races. While the tools of the past made it difficult to reason about these issues, Swift&amp;rsquo;s new tools make it a breeze, including the Sendable protocol, @Sendable closu&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.pointfree.co&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;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;  이전 내용&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #5b9aff;&quot; href=&quot;https://iwntberich.tistory.com/100&quot;&gt;1. 동시성의 과거: Thread&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #5b9aff;&quot; href=&quot;https://iwntberich.tistory.com/100&quot;&gt;2. 동시성프로그래밍: OperationQueue, GCD, Combine&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://iwntberich.tistory.com/102&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;3. 동시성프로그래밍: Task&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Concurrency's Future: Sendable and Actors&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sendable and @Sendable&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 동기화 및 Data Race 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&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;이전에 스레드와 DispatchQueue에 관련하여 다루었을 때 데이터 동기화 및 경쟁 문제가 있었다.&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 class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class Counter {
  let lock = NSLock()
  var count = 0
  func increment() {
    self.lock.lock()
    defer { self.lock.unlock() }
    self.count += 1
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;NSLock&lt;/code&gt; 을 사용하여 count 변수를 증가시킬 때 여러 스레드가 동시에 작업하지 못하도록 막아줬다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let counter = Counter()

for _ in 0..&amp;lt;1000 {
  Task {
    counter.increment()
  }
}

Thread.sleep(forTimeInterval: 2)
print(&quot;counter.count&quot;, counter.count)

// 출력:
// counter.count 1000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 1,000개의 태스크를 만들어 count를 증가시키도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 항상 1,000이 출력되는 것으로 보아 경쟁 상태를 해결한 것으로 볼 수 있다.&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;하지만 락을 사용하는 코드는 100% 안전을 보장할 수 없다. 코드가 복잡해지고 실수하기 쉬워 큰 문제를 발생시킬 가능성이 있다.&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;Swift의 제한&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;제한된 사항 중 하나는 동시성 컨텍스트에서 가변 변수(var)를 캡쳐하는 것을 금지하고 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func doSomething() {
  var count = 0
  Task {
    print(count) // Error
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Error: Reference to captured var &amp;lsquo;count&amp;rsquo; in concurrently-executing code&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;count = 0
Task {
  count = 1
}
Thread.sleep(forTimeInterval: 1)
print(count) // 0? 1?&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var count = 0
for _ in 0..&amp;lt;workCount {
  Thread.detachNewThread {
    count += 1
  }
}
Thread.sleep(forTimeInterval: 2)
print(&quot;count&quot;, count)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 스레드 기반 코드에서는 컴파일러가 경쟁 상태임을 인지하지 못하기 때문에 오류가 발생하지 않고, 1,000보다 작은 값을 출력하게 된다.&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;✔️ Immutable capture&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가변 캡쳐는 안되지만 불편 타입의 캡쳐는 문제가 발생하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;let count = 0
Task {
  print(count)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;let&lt;/code&gt; 을 사용하거나,&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;var count = 0
Task { [count] in
  print(count)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;var&lt;/code&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;span&gt; &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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;.executableTarget(
  name: &quot;concurrency&quot;,
  dependencies: [],
  swiftSettings: [
    .unsafeFlags([
      &quot;-Xfrontend&quot;, &quot;-warn-concurrency&quot;,
    ]),
  ]
),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 설정을 통해 플래그를 활성화 하게 되면&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for _ in 0..&amp;lt;1000 {
  Task {
    counter.increment() // warning
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 예시로 사용했던 코드에서 경고가 발생하게 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Capture of &amp;lsquo;counter&amp;rsquo; with non-sendable type &amp;lsquo;Counter&amp;rsquo; in a @Sendable closure&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경고는 @Sendable 클로저 내에서 non-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;먼저, Sendable 프로토콜에 대해 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;The Sendable protocol&lt;/h2&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;/// Sendable 프로토콜은 특정 타입의 값이 
/// 동시성 코드에서 안전하게 사용될 수 있음을 나타냅니다.
public protocol Sendable {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sendable 타입은 &lt;b&gt;Sendable프로토콜을 준수하는 타입&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sendable 프로토콜은 요구사항이 없는 프로토콜이지만 &lt;b&gt;컴파일러가 내부적&lt;/b&gt;으로 Sendable 타입이 프로토콜을 준수하는지 확인하는 작업을 수행한다. 이를 통해 &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;예를 들어,&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var count = 0
Task { [count] in
  print(count)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 경우는 문제가 발생하지 않는다. 오류가 없는 이유는 Int 타입이 Sendable 프로토콜을 준수하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준 라이브러리의 대부분의 타입은 값 타입으로 구성되어 있기 때문에 기본적으로 Sendable이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 필드가 모두 Sendable인 값 타입은 동시성 코드에서 안전하게 전달될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct User {
  var id: Int
  var name: String
}
let user = User(id: 42, name: &quot;Blob&quot;)
Task {
  print(user)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 데이터 타입이 더 복잡해지면 자동으로 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;code&gt;AttributedString&lt;/code&gt; 타입의 필드를 추가하게 된다면&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct User {
  var id: Int
  var name: String
  var bio: AttributedString
}
let user = User(id: 42, name: &quot;Blob&quot;, bio: &quot;&quot;)
Task {
  print(user) // warning
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Capture of &amp;lsquo;user&amp;rsquo; with non-sendable type &amp;lsquo;User&amp;rsquo; in a @Sendable closure&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Sendable&lt;/code&gt; 클로저 내에서 User 타입을 캡쳐하지 못한다는 경고가 발생한다. &lt;code&gt;AttributedString&lt;/code&gt;타입은 Sendable이 아니기 때문에 User타입은 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;위에서 다루었던 내용들은 값 타입이지만 참조타입도 Sendable로 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class User: Sendable { // warning 1
  var id: Int // warning 2
  var name: String
  init(id: Int, name: String) {
    self.id = id
    self.name = name
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경고:&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;1. Non-final class &amp;lsquo;User&amp;rsquo; cannot conform to &amp;lsquo;Sendable&amp;rsquo;; use &amp;rsquo;@unchecked Sendable&amp;rsquo;
2. Stored property &amp;lsquo;id&amp;rsquo; of &amp;lsquo;Sendable&amp;rsquo;-conforming class &amp;lsquo;User&amp;rsquo; is mutable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 경고는 &lt;code&gt;final&lt;/code&gt;이 아닌 클래스는 Sendable로 추론할 수 없다는 것을 나타낸다. 이는 서브클래스가 생성되어 가변 상태가 추가될 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 경고는 Sendable이 되려면 가변 필드가 포함될 수 없음을 나타낸다. 따라서 필드를 &lt;code&gt;let&lt;/code&gt;으로 변경해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;final class User: Sendable {
  let id: Int
  let name: String
  init(id: Int, name: String) {
    self.id = id
    self.name = name
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 수정하면 Sendable을 준수하는 타입이 만들어지지만 User 클래스의 많은 기능이 제한된다. 내부 데이터를 변경할 수 없고, 구초제와 비슷한 동작을 하게 된다.&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;p data-ke-size=&quot;size16&quot;&gt;이러한 경우 직접 문제를 해결하고, 컴파일러의 감시 범위에서 벗어나야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class Counter {
  let lock = NSLock()
  var count = 0
  func increment() {
    self.lock.lock()
    defer { self.lock.unlock() }
    self.count += 1
  }
}
let counter = Counter()
Task {
  counter.increment() // warning 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 Task 구문 내부에서 다음과 같은 경고가 나타난다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Capture of &amp;lsquo;counter&amp;rsquo; with non-sendable type &amp;lsquo;Counter&amp;rsquo; in a @Sendable closure&lt;/code&gt;&lt;/pre&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;만약 Counter를 Sendable로 만들게 된다면&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class Counter: Sendable { // warning 1
  let lock = NSLock() // warning 2
  var count = 0 // warning 3
  func increment() {
    self.lock.lock()
    defer { self.lock.unlock() }
    self.count += 1
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경고:&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;1. Non-final class &amp;lsquo;Counter&amp;rsquo; cannot conform to &amp;lsquo;Sendable&amp;rsquo;; use &amp;lsquo;@unchecked Sendable&amp;rsquo;
2. Stored property &amp;lsquo;lock&amp;rsquo; of &amp;lsquo;Sendable&amp;rsquo;-conforming class &amp;lsquo;Counter&amp;rsquo; has non-sendable type &amp;lsquo;NSLock&amp;rsquo;
3. Stored property &amp;lsquo;count&amp;rsquo; of &amp;lsquo;Sendable&amp;rsquo;-conforming class &amp;lsquo;Counter&amp;rsquo; is mutable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NSLock은 우리가 제어할 수 있는 타입이 아니기 때문에 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;@unchecked&lt;/b&gt;로 알려줄 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class Counter: @unchecked Sendable {
  let lock = NSLock()
  var count = 0
  func increment() {
    self.lock.lock()
    defer { self.lock.unlock() }
    self.count += 1
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 경고가 발생하지 않고 컴파일이 되지만 위의 코드는 컴파일러가 안전성을 체크하지 않기 때문에 조심해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Sendable closures&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Sendable 클로저&lt;/b&gt;는 &lt;b&gt;클로저가 동시성 작업에서 사용되는 것&lt;/b&gt;을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이해하기 위해 먼저 &lt;b&gt;@escaping 클로저&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func perform(work: () -&amp;gt; Void) {
  work()
}&lt;/code&gt;&lt;/pre&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;예를들어, 네트워크 통신을 통해 다운로드 작업을 완료한 후 클로저를 호출해보면 경고가 나타난다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func perform(work: () -&amp;gt; Void) {
  print(&quot;Begin&quot;)
  URLSession.shared.dataTask(
    with: .init(string: &quot;&amp;lt;http://ipv4.download.thinkbroadband.com/1MB.zip&amp;gt;&quot;)!
  ) { _, _, _ in
    work()
  }
  print(&quot;End&quot;)
}

// Warning: Escaping closure captures non-escaping parameter &amp;lsquo;work&amp;rsquo;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Escaping 클로저&lt;/b&gt;는 &lt;b&gt;함수 실행 범위가 끝난 후에도 참조되어야 하는 클로저&lt;/b&gt;이다. dataTask의 경우 호출 즉시 반환되기 때문에 &lt;b&gt;클로저가 함수 실행 범위 이후까지 유효해야 한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 Non-escaping 클로저를 호출하게 되면 컴파일러가 경고하게 된다.&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에서는 &lt;b&gt;클로저가 함수 실행 범위를 벗어나는지에 대한 여부를 중요하게 생각&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 뒷받침 하기 위한 예시를 살펴보자면,&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func incrementAfterOneSecond(value: inout Int) {
  DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
    value += 1
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초 후에 값을 증가시키는 함수가 있다고 가정해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var count = 0
incrementAfterOneSecond(value: &amp;amp;count)
assert(count == 0)
Thread.sleep(forTimeInterval: 2)
assert(count == 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드가 유효한 코드라고 가정했을 때, 첫번째 assert에서는 0일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 2초 대기 후 값이 자동으로 1로 업데이트가 된다면 뭔가 어색한 동작으로 보인다. 왜냐하면 &lt;b&gt;count는 값 타입&lt;/b&gt;인데 &lt;b&gt;참조타입처럼 동작&lt;/b&gt;하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 &lt;b&gt;원격 조작(spooky action at a distance)&lt;/b&gt;은 &lt;b&gt;값 타입의 본질을 무너뜨리기 때문&lt;/b&gt;에 Swift에서는 &lt;b&gt;Escaping 클로저&lt;/b&gt;를 통해 이러한 동작을 방지하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 Swift에서는 inout 값을 Escaping 클로저 내부로 전달하는 것은 허용되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func perform(work: @escaping () -&amp;gt; Void) {
  print(&quot;Begin&quot;)
  DispatchQueue(label: &quot;delay&quot;).asyncAfter(deadline: .now() + 1) {
    work()
  }
  print(&quot;End&quot;)
}
perform {
  print(&quot;Hello&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 work클로저를 &lt;code&gt;asyncAfeter&lt;/code&gt; 내에서 실행할 수 있도록 하기 위해서는 &lt;code&gt;@escaping&lt;/code&gt;을 명시적으로 추가해야 한다. &lt;code&gt;@escaping&lt;/code&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;그치만 async 를 통해 &lt;code&gt;@escaping&lt;/code&gt;이 필요하지 않은 클로저를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func perform(work: () -&amp;gt; Void) async throws {
  print(&quot;Begin&quot;)
  _ = try await URLSession.shared.data(from: .init(string: &quot;&amp;lt;http://ipv4.download.thinkbroadband.com/1MB.zip&amp;gt;&quot;)!)
  work()
  print(&quot;End&quot;)
}

Task {
  try await perform {
    print(&quot;Hello&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Begin
Hello
End&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async 를 사용하게 되면서 work 클로저가 &lt;b&gt;함수 범위 내에서만 호출&lt;/b&gt;된다. await 키워드를 통해 &lt;b&gt;작업이 완료될 때 까지 대기&lt;/b&gt;하게되어 @escaping 키워드 없이도 문제가 발생하지 않게 된다.&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;그렇기 때문에 Escaping 클로저에서는 사용이 불가능했던 inout값 또한 처리할 수 있게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func perform(value: inout Int, work: () -&amp;gt; Void) async throws {
  print(&quot;Begin&quot;)
  let (data, _) = try await URLSession.shared.data(from: .init(string: &quot;&amp;lt;http://ipv4.download.thinkbroadband.com/1MB.zip&amp;gt;&quot;)!)
  work()
  value += data.count
  print(&quot;End&quot;)
}

Task {
  var count = 0
  try await perform(value: &amp;amp;count) {
    print(&quot;Hello&quot;)
  }
  print(&quot;count&quot;, count)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Begin
Hello
End
count 1048576&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ 동시성 작업에서의 Non-escaping&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, work 클로저를 여러 번 동시에 실행하고싶어 두개의 Task를 생성하게 된다면 오류가 발생하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mLNL8/btsLA70mZv8/X76kiKaWNBjfAgwoQvw2v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mLNL8/btsLA70mZv8/X76kiKaWNBjfAgwoQvw2v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mLNL8/btsLA70mZv8/X76kiKaWNBjfAgwoQvw2v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmLNL8%2FbtsLA70mZv8%2FX76kiKaWNBjfAgwoQvw2v0%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;618&quot; height=&quot;261&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Task{}&lt;/code&gt;는 클로저를 &lt;b&gt;생성된 태스크 이후에 호출&lt;/b&gt;해야 하기 때문에 &lt;b&gt;escaping 클로저&lt;/b&gt;여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QLSSS/btsLBywpZyw/soEvbeFg282ylExqeArhk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QLSSS/btsLBywpZyw/soEvbeFg282ylExqeArhk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QLSSS/btsLBywpZyw/soEvbeFg282ylExqeArhk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQLSSS%2FbtsLBywpZyw%2FsoEvbeFg282ylExqeArhk1%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;622&quot; height=&quot;233&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여전히 오류가 발생하는데, 그 이유는 &lt;code&gt;@Sendable&lt;/code&gt;이 필요한 컨텍스트 내에서 @Sendable이 아닌 데이터를 사용하고 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저가 외부 데이터를 캡쳐하고 수정하게 된다면 &lt;b&gt;race condition&lt;/b&gt;문제를 일으킬 수 있기 때문에 동시성 작업을 할 때는 안전한 클로저를 요구하게 된다. &lt;code&gt;@Sendable&lt;/code&gt;이 필요한 이유이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func perform(work: @escaping @Sendable () -&amp;gt; Void) {
  &amp;hellip;
}&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;@Sendable클로저는 &lt;b&gt;외부의 가변 상태 데이터를 캡쳐하거나 변경하지 못하도록 한다&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1451&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n8W1S/btsLAfZdiTJ/7Xhj4yaOPWuersRvxP7Kj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n8W1S/btsLAfZdiTJ/7Xhj4yaOPWuersRvxP7Kj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n8W1S/btsLAfZdiTJ/7Xhj4yaOPWuersRvxP7Kj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn8W1S%2FbtsLAfZdiTJ%2F7Xhj4yaOPWuersRvxP7Kj0%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;610&quot; height=&quot;327&quot; data-origin-width=&quot;1451&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Sendable 클로저를 추가하게되면서 Swift는 클로저가 동시성 작업에서 사용된다는 것을 알게된다. 그렇기 때문에 &lt;code&gt;count&lt;/code&gt;라는 &lt;b&gt;가변 변수를 캡쳐하지 못하도록 제한&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Sendable&lt;/b&gt; 속성은 함수가 &lt;b&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;code&gt;@Sendable&lt;/code&gt; 은 내부에 클로저를 가지고 있는 타입이 &lt;b&gt;Sendable&lt;/b&gt;프로토콜을 준수하도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어, 데이터베이스에 접근하는 작업을 추상화하여 설계한다고 가정해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct DatabaseClient {
  var fetchUsers: () async throws -&amp;gt; [User]
  var createUser: (User) async throws -&amp;gt; Void
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 구조체가 있다고 할 때 위의 구조체는 동시성 작업에서 안전하게 사용하지 못한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kx0Av/btsLA7lQYjB/79YzA6o0og9OreGxYHpO5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kx0Av/btsLA7lQYjB/79YzA6o0og9OreGxYHpO5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kx0Av/btsLA7lQYjB/79YzA6o0og9OreGxYHpO5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkx0Av%2FbtsLA7lQYjB%2F79YzA6o0og9OreGxYHpO5k%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;691&quot; height=&quot;245&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 DatabaseClient가 &lt;b&gt;Sendable&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;그렇다면 명시적으로 DatabaseClient를 Sendable 을 준수하도록 한다면, 내부 프로퍼티가 Sendable 타입이 아니기 때문에 또 문제가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 클로저는 sendable 타입이 아닌 어떠한 클로저든 가능하기 때문에 가변 데이터를 읽고 쓸 가능성이 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct DatabaseClient {
  var fetchUsers: @Sendable () async throws -&amp;gt; [User]
  var createUser: @Sendable (User) async throws -&amp;gt; Void
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DatabaseClient가 Sendable타입을 충족하려면 내부 프로퍼티의 클로저를 @Sendable로 명시적으로 지정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Sendable&lt;/code&gt;을 통해 &lt;b&gt;사용할 수 있는 클로저의 종류를 제한&lt;/b&gt;하여 컴파일러에게 &lt;b&gt;sendable 타입임을 증명&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Actor&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Sendable을 통해 동시성 작업에서의 안정성을 보장할 수 있게 되었다. Sendable을 만족하지 않을 경우 컴파일러가 문제를 알려주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;code&gt;@unchecked Sendable&lt;/code&gt;은 Sendable을 준수하는지 컴파일러가 확인하지 않고 &lt;b&gt;강제적으로&lt;/b&gt; 준수한다고 컴파일러에게 알려주게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class Counter: @unchecked Sendable {
  let lock = NSLock()
  var count = 0
  func increment() {
    self.lock.lock()
    defer { self.lock.unlock() }
    self.count += 1
  }
}&lt;/code&gt;&lt;/pre&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;Race Condition 문제&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;이러한 Data Race 발생 위험성 문제를 해결하기 위해 Swift 5.5에서는 새로운 타입인 &lt;b&gt;Actor&lt;/b&gt;가 도입되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor는 구조체, 열거형, 클래스와 같이 &lt;b&gt;새로운 타입&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;actor counterActor {
}&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;actor CounterActor {
  var count = 0
  var maximum = 0
  func increment() {
    self.count += 1
    self.maximum = max(self.count, self.maximum)
  }
  func decrement() {
    self.count -= 1
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 사용한 코드와 같이 클래스로 구현할 경우 동시성 환경에서 count 변수에 대해 data race 발생 가능성이 존재했지만, actor 타입을 사용할 경우 이러한 문제가 해결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor를 사용하게 되면 &lt;b&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;✔️ Actor의 DataRace 방지&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciI0ke/btsLBza0Dtu/RBX3f4KAvZGuoZy5GuDgJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciI0ke/btsLBza0Dtu/RBX3f4KAvZGuoZy5GuDgJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciI0ke/btsLBza0Dtu/RBX3f4KAvZGuoZy5GuDgJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciI0ke%2FbtsLBza0Dtu%2FRBX3f4KAvZGuoZy5GuDgJ1%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;616&quot; height=&quot;272&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 스레드를 생성하여 &lt;code&gt;increment()&lt;/code&gt;메서드를 호출하려 하면 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 오류는 Swift가 DataRace를 발생시킬 수 있는 작업을 하지 못하도록 막는 것이다.&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;Actor의 핵심 원칙은 내부 데이터를 보호&lt;/b&gt;이기 때문에 Actor 내부의 메서드는 &lt;b&gt;비동기 컨텍스트에서만 호출&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 스레드에서 &lt;code&gt;increment()&lt;/code&gt;메서드에 접근하려고 할 경우 이전 작업이 완료될 때 까지 다른 스레드는 기다려야 한다. 하지만 스레드가 대기 상태에 있으면 리소스가 낭비될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor는 락을 사용하지 않고 &lt;b&gt;비동기 방식으로 동기화 처리&lt;/b&gt;를 한다. 스레드가 대기해야 할 경우 &lt;b&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;그렇기 때문에 Actor내의 메서드를 호출하기 위해서는 &lt;b&gt;비동기 컨텍스트에서 await 키워드를 통해 호출&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for _ in 0..&amp;lt;1000 {
  Task {
    await counter.increment()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor를 통해 &lt;b&gt;데이터 보호&lt;/b&gt;와 &lt;b&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;Actor 내부에서는 &lt;code&gt;increment()&lt;/code&gt; 메서드를 &lt;code&gt;async&lt;/code&gt;로 선언하지 않았지만, &lt;code&gt;await&lt;/code&gt;을 사용하여 호출해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Actor 내부&lt;/b&gt;에서만 보면 &lt;code&gt;increment()&lt;/code&gt;는 &lt;b&gt;동기적&lt;/b&gt;이다. Actor 내부는 &lt;b&gt;완전히 동기화 된 컨텍스트 내에 있기 때문&lt;/b&gt;에 &lt;b&gt;가변 데이터를 자유롭게 읽고 쓸 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func increment() {
  self.count += 1
  self.maximum = max(self.maximum, self.count)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 Actor 내에 있는 메서드 내에서도 프로퍼티나 또 다른 메서드를 호출할 때 &lt;code&gt;await&lt;/code&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;Actor 외부&lt;/b&gt;에서는 여러 작업이 동시에 &lt;code&gt;increment()&lt;/code&gt;메서드를 호출할 수 있기 때문에 동기적으로 호출할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor는 순차적으로 처리하기 위한 &lt;b&gt;동기화 작업이 필요&lt;/b&gt;하고, 효율적으로 처리되기 위해서는 &lt;b&gt;비동기 컨텍스트에서 작동&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7vPXR/btsLCIE4R55/XfZkau7e3LiumVj6gMKLwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7vPXR/btsLCIE4R55/XfZkau7e3LiumVj6gMKLwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7vPXR/btsLCIE4R55/XfZkau7e3LiumVj6gMKLwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7vPXR%2FbtsLCIE4R55%2FXfZkau7e3LiumVj6gMKLwk%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;707&quot; height=&quot;97&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 뿐만아니라 &lt;b&gt;프로퍼티 접근&lt;/b&gt; &lt;b&gt;또한&lt;/b&gt; 비동기 환경에서 이루어져야 한다. 다른 작업에서 프로퍼티 수정을 할 경우 잘못된 값을 얻을 수 있기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Thread.sleep(forTimeInterval: 1)
Task {
  await print(&quot;counter.count&quot;, counter.count)
}&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를 통해 우리는 동기 코드처럼 코드를 작성할 수 있고, datarace 문제에 신경쓰지 않아도 된다. Actor 외부에서 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;만약 1000 번의 증가, 감소 작업을 수행 중에 count와 maximum 값을 출력시키면 어떤 결과가 나올까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIMhVY/btsLzZhFkRE/Y9NDbrxo8haz6R4kFj85L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIMhVY/btsLzZhFkRE/Y9NDbrxo8haz6R4kFj85L1/img.png&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;654&quot; data-is-animation=&quot;false&quot; width=&quot;404&quot; height=&quot;286&quot; data-widthpercent=&quot;47.98&quot; style=&quot;width: 388px; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIMhVY/btsLzZhFkRE/Y9NDbrxo8haz6R4kFj85L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIMhVY%2FbtsLzZhFkRE%2FY9NDbrxo8haz6R4kFj85L1%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;886&quot; height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b41pQz/btsLBWKAnQb/hmBxCNdxiKsvLp2T5KTuF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b41pQz/btsLBWKAnQb/hmBxCNdxiKsvLp2T5KTuF0/img.png&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;674&quot; data-is-animation=&quot;false&quot; width=&quot;411&quot; height=&quot;282&quot; style=&quot;width: 414px;&quot; data-widthpercent=&quot;52.02&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b41pQz/btsLBWKAnQb/hmBxCNdxiKsvLp2T5KTuF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb41pQz%2FbtsLBWKAnQb%2FhmBxCNdxiKsvLp2T5KTuF0%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;990&quot; height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;같은 코드인데 maximum 값이 다르게 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 race condition을 의미하는 것이 아니다. 이는 &lt;b&gt;비 결정론적인 예시&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000 번의 증가와 1000번의 감소 작업이 동시에 실행되고 있는데, &lt;b&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;즉, 어떠한 경우에는 increment 작업이 연속적으로 실행되어 maximum값이 높아질 수 있고, 다른 경우에는 increment와 decrement 작업이 번갈아 실행될 수 있다.&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;b&gt;우선순위를 어떻게 지정하느냐&lt;/b&gt;에 달려있기 때문에 &lt;b&gt;비결정론이 도입&lt;/b&gt;되어있는 것을 의미한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 새로운 동시성 도구는 기존의 동시성 도구보다 훨씬 강력한 기능을 가지고 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Swift 언어와의 통합&lt;br /&gt;비동기와 동시성 개념이 언어에 직접적으로 통합되어있다. &lt;b&gt;async&lt;/b&gt; 키워드를 통해 비동기 작업을 명확히 표현할 수 있으며, Sendable 프로토콜과 @Sendable 속성을 통해 동시성 환경에서 사용 가능한 데이터를 표현할 수 있게 되었다.&lt;/li&gt;
&lt;li&gt;효율적인 작업 관리&lt;br /&gt;명시적으로 스레드 풀이나 작업 큐를 관리하지 않더라도 Swift는 &lt;b&gt;생성되는 스레드 수를 제한&lt;/b&gt;하며 &lt;b&gt;수천개의 동시 작업을 수행&lt;/b&gt;할 수 있게 되었다.&lt;/li&gt;
&lt;li&gt;향상된 Task&lt;br /&gt;Task는 기존 도구들의 기능을 모두 가지고 있을 뿐만아니라 기능들이 더 향상되었다. 취소나 저장 기능은 하위 Task에도 전달이 가능해졌다.&lt;/li&gt;
&lt;li&gt;효율적인 스레드 사용&lt;br /&gt;Non-blocking 비동기 함수나 Task.yield와 같은 도구를 통해 &lt;b&gt;스레드를 점유하지 않고 다른 작업이 스레드를 사용할 수 있도록 중단할 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Actor&lt;br /&gt;Swift는 &lt;b&gt;Actor&lt;/b&gt;라는 새로운 타입을 통해 &lt;b&gt;컴파일러가 Data race 문제를 감지할 수 있게 되었다&lt;/b&gt;. Actor를 통해 코드를 &lt;b&gt;간단하고 동기 코드 처럼 작성&lt;/b&gt;할 수 있지만 &lt;b&gt;내부적으로는 가변 데이터에 대한 접근을 제한&lt;/b&gt;하는 기능을 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  다음 내용&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://iwntberich.tistory.com/103&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;4. 동시성프로그래밍: Sendable, Actor&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS/Swift</category>
      <category>actor</category>
      <category>concurrency</category>
      <category>ios</category>
      <category>pointfree</category>
      <category>Sendable</category>
      <category>Swift</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/103</guid>
      <comments>https://iwntberich.tistory.com/103#entry103comment</comments>
      <pubDate>Mon, 30 Dec 2024 14:27:54 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/Point-Free] 동시성프로그래밍: Task</title>
      <link>https://iwntberich.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;PointFree 강의 정리 내용입니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/episodes/ep192-concurrency-s-future-tasks-and-cooperation#task-cooperation&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.pointfree.co/episodes/ep192-concurrency-s-future-tasks-and-cooperation#task-cooperation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734505213895&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;Episode #192: Concurrency's Future: Tasks and Cooperation&quot; data-og-description=&quot;Let&amp;rsquo;s look at what the future of concurrency looks like in Swift. A recent release of Swift came with a variety of tools with concurrency. Let&amp;rsquo;s examine its fundamental unit in depth, and explore how they &amp;ldquo;cooperate&amp;rdquo; in your applications.&quot; data-og-host=&quot;www.pointfree.co&quot; data-og-source-url=&quot;https://www.pointfree.co/episodes/ep192-concurrency-s-future-tasks-and-cooperation#task-cooperation&quot; data-og-url=&quot;https://www.pointfree.co/episodes/ep192-concurrency-s-future-tasks-and-cooperation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/do2ZPB/hyXOhoGHJ7/9SMnY73vzYJOyXHoONOd60/img.jpg?width=1400&amp;amp;height=788&amp;amp;face=214_207_1197_616,https://scrap.kakaocdn.net/dn/be9Wru/hyXOjGOj9p/plxtO6unYpkE1ratuVKAr0/img.jpg?width=1400&amp;amp;height=788&amp;amp;face=214_207_1197_616&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/episodes/ep192-concurrency-s-future-tasks-and-cooperation#task-cooperation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.pointfree.co/episodes/ep192-concurrency-s-future-tasks-and-cooperation#task-cooperation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/do2ZPB/hyXOhoGHJ7/9SMnY73vzYJOyXHoONOd60/img.jpg?width=1400&amp;amp;height=788&amp;amp;face=214_207_1197_616,https://scrap.kakaocdn.net/dn/be9Wru/hyXOjGOj9p/plxtO6unYpkE1ratuVKAr0/img.jpg?width=1400&amp;amp;height=788&amp;amp;face=214_207_1197_616');&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;Episode #192: Concurrency's Future: Tasks and Cooperation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Let&amp;rsquo;s look at what the future of concurrency looks like in Swift. A recent release of Swift came with a variety of tools with concurrency. Let&amp;rsquo;s examine its fundamental unit in depth, and explore how they &amp;ldquo;cooperate&amp;rdquo; in your applications.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.pointfree.co&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;p data-ke-size=&quot;size16&quot;&gt;  이전 내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://iwntberich.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1. 동시성의 과거: Thread&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://iwntberich.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2. 동시성프로그래밍: OperationQueue, GCD, Combine&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;Concurrency's Future: Tasks and Cooperation&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Task Basic&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Task&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Task&lt;/b&gt;는 Swift 5.5부터 새로 출시된 동시성 도구이다. &lt;b&gt;Task&lt;/b&gt;는 비동기작업을 실행하는 기본 단위이고, 다음과 같이 생성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Task {
  print(Thread.current)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;&amp;lt;NSThread: 0x1062040f0&amp;gt;{number = 2, name = (null)}&lt;/code&gt;&lt;/pre&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;이 내용은 Thread의 &lt;code&gt;detachNewThread&lt;/code&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의 타입은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/USHd4/btsLl0apa0I/6LodO0ms0fOyodfx4JEyck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/USHd4/btsLl0apa0I/6LodO0ms0fOyodfx4JEyck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/USHd4/btsLl0apa0I/6LodO0ms0fOyodfx4JEyck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUSHd4%2FbtsLl0apa0I%2F6LodO0ms0fOyodfx4JEyck%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;614&quot; height=&quot;518&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Void와 Never 두 개의 제네릭을 반환하게 된다.&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;다음과 같이 실행 결과의 타입과 에러 타입을 정의할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let task: Task&amp;lt;Int, Error&amp;gt; = Task {
  struct SomeError: Error {}
  throw SomeError()
  return 42
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;async/await&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task의 초기화 구문에 있는 클로저에는 &lt;b&gt;async&lt;/b&gt;키워드가 포함되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZh31x/btsLlRYtp5f/lwLah7wisb148WpKwPbfhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZh31x/btsLlRYtp5f/lwLah7wisb148WpKwPbfhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZh31x/btsLlRYtp5f/lwLah7wisb148WpKwPbfhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZh31x%2FbtsLlRYtp5f%2FlwLah7wisb148WpKwPbfhk%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;584&quot; height=&quot;258&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;async&lt;/b&gt;는 해당 키워드가 붙은 함수에서 &lt;b&gt;비동기 작업을 수행할 것임을 선언하는 것을 의미&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;async&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;예를 들어, 다음과 같은 async로 선언된 함수가 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func doSomethingAsync() async {
}&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;doSomethingAsync() // 컴파일 에러 발생&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 컨텍스트 내부에서 호출할 수 있으며, &lt;b&gt;await&lt;/b&gt;키워드를 붙여야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Task {
  await doSomethingAsync()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task는 새로운 비동기 컨텍스트를 생성하기 때문에 위와 같이 비동기 함수 호출이 가능한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;await&lt;/b&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func doSomethingElseAsync() async {
  await doSomethingAsync()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식은 이전에 스레드와 큐를 사용한 방식과 완전히 다르다. 이전에는 비동기 코드와 동기 코드 간에 컴파일러 단계에서 구분을 하지 않았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동기-비동기 코드의 구분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 오류를 던질 수 있는 함수는 &lt;code&gt;throws&lt;/code&gt;로 명시해야하며, 호출 시 실패에 대한 처리를 해야 호출할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func doSomethingThatCanFail() throws {} // 오류 가능 함수

doSomethingThatCanFail() // 오류
try doSomethingThatCanFail() // try로 실패에 대한 처리 - 호출 성공&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 새로운 함수 내에서 그냥 호출하면 문제가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func doSomething() {
  try doSomethingThatCanFail()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해서는 함수를 실패할 수 있는 컨텍스트로 만들거나 &lt;b&gt;do-catch&lt;/b&gt; 구문을 통해 실패 가능 컨텍스트를 생성하여 실패 시 오류를 처리할 수 있는 작업을 정의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func doSomethingElseThatCanFail() throws {
  try doSomethingThatCanFail()
}

// 또는 

func doSomething() {
  do {
    try doSomethingThatCanFail()
  } catch {
    // 오류 처리
  }
}&lt;/code&gt;&lt;/pre&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;h3 data-ke-size=&quot;size23&quot;&gt;syntactic sugar&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에서 사용하는 키워드들은 복잡한 동작을 간단히 표현하는 &lt;b&gt;syntactic sugar&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;throws&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류를 던지는 &lt;code&gt;throws&lt;/code&gt; 키워드를 가진 함수는 &lt;code&gt;Result&amp;lt;&amp;gt;&lt;/code&gt; 타입을 반환하는 함수로 풀어 설명할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;(A) throws -&amp;gt; B

(A) -&amp;gt; Result&amp;lt;B, Error&amp;gt;&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;async&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 함수에서 &lt;code&gt;async&lt;/code&gt; 키워드를 제거하고 &lt;code&gt;Task&amp;lt;&amp;gt;&lt;/code&gt; 타입을 반환하도록 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;(A) async -&amp;gt; B

(A) -&amp;gt; Task&amp;lt;B, Never&amp;gt;&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 타입을 다음과 같이 풀어 설명할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;(A) -&amp;gt; ((B) -&amp;gt; Void) -&amp;gt; Void&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;(A, (B)-&amp;gt;Void) -&amp;gt; Void&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형태는 콜백 핸들러의 기본 형태이다. 대부분의 Apple 비동기 api는 이러한 형태를 사용하였다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// URLSession의 콜백 핸들러 예시
dataTask: (URL, (Data?, Response?, Error?) -&amp;gt; Void) -&amp;gt; Void&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;위와 같은 형태는 Swift 언어 자체에서 비동기를 지원하게 되며 &lt;code&gt;async&lt;/code&gt; 키워드를 통해 복잡한 구문을 간단하게 나타낼 수 있게 되었다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;(A) async -&amp;gt; B&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Task의 스레드 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task에서 여러 개의 작업을 실행하면 여러 개의 스레드가 생성된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Task { print(&quot;1&quot;, Thread.current) }
Task { print(&quot;2&quot;, Thread.current) }
Task { print(&quot;3&quot;, Thread.current) }
Task { print(&quot;4&quot;, Thread.current) }
Task { print(&quot;5&quot;, Thread.current) }&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;2 &amp;lt;NSThread: 0x1062040f0&amp;gt;{number = 3, name = (null)}
1 &amp;lt;NSThread: 0x106004bd0&amp;gt;{number = 4, name = (null)}
3 &amp;lt;NSThread: 0x106105c90&amp;gt;{number = 5, name = (null)}
4 &amp;lt;NSThread: 0x106004430&amp;gt;{number = 6, name = (null)}
5 &amp;lt;NSThread: 0x106007f40&amp;gt;{number = 7, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Thread나 Operation Queue에서 처럼 실행 순서는 보장되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 DispatchQueue에서와 달리 기본적으로 &lt;b&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;그렇다면 1,000개의 작업을 생성할 때 마다 스레드가 생성될까?&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for n in 0..&amp;lt;1000 {
  Task {
    print(n, Thread.current)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;1 &amp;lt;NSThread: 0x1011caaa0&amp;gt;{number = 2, name = (null)}
0 &amp;lt;NSThread: 0x101304100&amp;gt;{number = 3, name = (null)}
14 &amp;lt;NSThread: 0x101304100&amp;gt;{number = 3, name = (null)}
...
988 &amp;lt;NSThread: 0x1011caaa0&amp;gt;{number = 2, name = (null)}
804 &amp;lt;NSThread: 0x1015040f0&amp;gt;{number = 7, name = (null)}
923 &amp;lt;NSThread: 0x100710190&amp;gt;{number = 11, name = (null)}
989 &amp;lt;NSThread: 0x10070e380&amp;gt;{number = 5, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 결과로 보아 대략 &lt;b&gt;10개 정도의 스레드만 생성&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;Task는 스레드 풀을 활용하여 &lt;b&gt;Thread Explosion문제를 해결&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, Task는 일정 시간 대기 후 작업을 스케줄링 할 수 있다. 기존에는 &lt;code&gt;sleep&lt;/code&gt; 을 사용하여 비효율적이게 처리하였는데, Task에서는 비동기로 동작하고 예외를 던질 수 있는 정적 메서드 &lt;b&gt;sleep&lt;/b&gt;이 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;try await Task.sleep(nanoseconds: UInt64)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 스레드에서 사용하던 &lt;code&gt;sleep&lt;/code&gt;과 매우 다른 메서드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Task.sleep()&lt;/code&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;예를 들어, 1000개의 작업을 생성하고 1초동안 중단하도록 하면&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for n in 0..&amp;lt;1000 {
  Task {
    try await Task.sleep(nanoseconds: NSEC_PER_SEC * 1000)
  }
}&lt;/code&gt;&lt;/pre&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;중단점을 통해 스레드를 확인해보면, 적은 수의 스레드가 생성되고, &lt;b&gt;단일 스택 프레임만 가지고 있음&lt;/b&gt;을 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Thread 2#0        0x00000001a05ef604 in __workq_kernreturn ()
Thread 3#0        0x00000001a05ef604 in __workq_kernreturn ()
Thread 4#0        0x00000001a05ef604 in __workq_kernreturn ()
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;\_\_workq\_kernreturn()&lt;/code&gt; 함수는 GCD가 스레드를 작업이 스케줄링될 때 까지 대기상태로 유지하는 방식이다. 이는 스레드가 아무 작업을 하지 않는 상태에서 &lt;b&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;code&gt;Task.sleep&lt;/code&gt; 은 &lt;b&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;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for _ in 1...workCount {
  Task {
    let current = Thread.current
    try await Task.sleep(nanoseconds: 1_000_000)
    if current != Thread.current {
      print(&quot;Thread changed from&quot;, current, &quot;to&quot;, Thread.current)
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;944 Thread changed from &amp;lt;NSThread: 0x10648a790&amp;gt;{number = 2, name = (null)} to &amp;lt;NSThread: 0x1063044c0&amp;gt;{number = 3, name = (null)}
945 Thread changed from &amp;lt;NSThread: 0x1063044c0&amp;gt;{number = 3, name = (null)} to &amp;lt;NSThread: 0x10648a790&amp;gt;{number = 2, name = (null)}
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 인접한 코드가 동일한 스레드에서 실행될 것이라고 가정해서는 안된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Task 우선순위와 취소&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우선순위&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task는 다른 동시성 도구들 처럼 우선순위 개념을 지원한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Task(priority: .low) {
  print(&quot;low&quot;)
}
Task(priority: .high) {
  print(&quot;high&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;high
low&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위를 통해 Task의 중요도를 전달할 수 있고, 런타임 시 여러 조건에 따라 작업마다 할당 시간을 적절히 부여할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;취소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취소 또한 다른 도구들과 마찬가지로 지원하는 개념이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let task = Task {
  print(Thread.current)
}
task.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;&amp;lt;NSThread: 0x106204290&amp;gt;{number = 2, name = (null)}&lt;/code&gt;&lt;/pre&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;code&gt;print&lt;/code&gt;구문이 그대로 출력된다.&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;b&gt;✔️ 협력적 취소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task의 작업을 즉시 중단하기 위해서는 &lt;b&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;/b&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;Task의 취소 여부를 확인하기 위해 &lt;code&gt;Task.isCancelled&lt;/code&gt; 를 통해 boolean 값을 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let task = Task {
  guard !Task.isCancelled else {
    print(&quot;Cancelled!&quot;)
    return
  }
  print(Thread.current)
}
task.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;Cancelled!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Task.isCancelled&lt;/code&gt; 는 현재 Task의 취소 상태를 나타낸다. 이는 전역변수가 아니라 Task에만 국한된 &lt;b&gt;로컬 값&lt;/b&gt;이기 때문에 현재 실행중인 Task의 취소 여부는 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;b&gt;✔️ 현재 Task&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task에는 &lt;code&gt;Thread.current&lt;/code&gt; 와 같이 &lt;code&gt;Task.current&lt;/code&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;현재 Task를 가져오기 위해서는 &lt;code&gt;withUnsafeCurrentTask&lt;/code&gt; 를 활용해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Task {
  withUnsafeCurrentTask { task in
    print(task)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Optional(Swift.UnsafeCurrentTask(_task: (Opaque Value)))&lt;/code&gt;&lt;/pre&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;&lt;b&gt;✔️ Task의 취소 여부: 예외처리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&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;비동기 작업에서 작업이 실패할 가능성이 있는 경우, 취소 여부를 확인하는 방식으로 &lt;code&gt;Task.isCancelled&lt;/code&gt; 를 통한 boolean값 검사 대신 &lt;b&gt;예외처리&lt;/b&gt;를 사용하여 효과적으로 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let task = Task {
  try Task.checkCancellation()
  print(Thread.current)
}
task.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Task.checkCancelled()&lt;/code&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;b&gt;✔️ 협력적 취소: 취소의 전달&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread나 DispatchQueue에서는 스레드를 &lt;code&gt;sleep&lt;/code&gt;상태로 만들거나 내부 작업이 생성되었을 때 취소가 전달되지 않는 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Task에서는 &lt;b&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;예를 들어, Task를 1초 동안 sleep 상태로 만든 후 0.1초 뒤에 취소해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let task = Task {
  let start = Date()
  defer { print(&quot;Task finished in&quot;, Date().timeIntervalSince(start)) }
  try await Task.sleep(nanoseconds: NSEC_PER_SEC)
  print(Thread.current) // 스레드 정보 출력
}

Thread.sleep(forTimeInterval: 0.1)
task.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Task finished in 0.10534894466400146&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 결과는 &lt;code&gt;Task.sleep&lt;/code&gt;이 취소 이벤트를 확인하고, sleep을 조기에 종료하였음을 의미한다.&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;b&gt;나머지 작업이 즉시 중단&lt;/b&gt;되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 비동기 함수 내부에서 sleep을 호출한 후 취소를 해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func doSomething() async throws {
  try await Task.sleep(nanoseconds: NSEC_PER_SEC)
}

let task = Task {
  let start = Date()
  defer { print(&quot;Task finished in&quot;, Date().timeIntervalSince(start)) }

  try await doSomething()
  print(Thread.current)
}

Thread.sleep(forTimeInterval: 0.1)
task.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Task finished in 0.10534894466400146&lt;/code&gt;&lt;/pre&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;스레드, OperationQueue, Dispatch Queue에서는 작업 단위의 취소 상태를 관찰하고 직접 취소를 구현해야 했지만 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;또 다른 예시로, URLSession을 통해 큰 파일을 다운로드하고, 다운로드가 완료되기 전에 취소를 해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let task = Task {
  let start = Date()
  defer { print(&quot;Task finished in&quot;, Date().timeIntervalSince(start)) }

  let (data, _) = try await URLSession.shared.data(
    from: URL(string: &quot;&amp;lt;http://ipv4.download.thinkbroadband.com/1MB.zip&amp;gt;&quot;)!
  )
  print(Thread.current, &quot;network request finished&quot;, data.count)
}

Thread.sleep(forTimeInterval: 0.5)
task.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Task finished in 0.5094579458236694&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;network request finished&amp;rdquo;가 출력되지 않는 것으로 보아 &lt;b&gt;Task는 네트워크 요청 진행 중일 때에도 취소를 감지하고 작업을 종료할 수 있다&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;Task의 협력적 취소는 &lt;b&gt;리소스를 절약&lt;/b&gt;할 수 있도록 동작하는 것을 볼 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Task Locals&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 스레드와 Dispatch Queue에서는 데이터를 저장하기 위해 &lt;b&gt;스레드 딕셔너리&lt;/b&gt;와 &lt;b&gt;specifics&lt;/b&gt;를 사용하였다.&lt;/p&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;스레드 딕셔너리는 [AnyHashable: Any] 타입을 사용했기 때문에 타입 안정성이 부족했고, 강제 캐스팅을 사용해야 했다.&lt;/li&gt;
&lt;li&gt;새로운 스레드를 생성하였을 때 기존의 스레드 딕셔너리를 상속할 수 없었다.&lt;/li&gt;
&lt;li&gt;DispatchQueue에서 기존 큐를 타겟으로 설정하여 specifics를 전달할 수 있었지만, 이를 명시적으로 값을 전달하는 작업을 구현해야 하고, 모든 큐에서 공통으로 접근할 수 있는 &amp;ldquo;현재 큐&amp;rdquo;의 개념이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task는 이러한 한계점을 보완하기 위해 &lt;b&gt;Task Local Values&lt;/b&gt;라는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task Local값을 사용하면 특정 값을 Task에 연결하고, 해당 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;b&gt;✔️ TaskLocal 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 예시로 사용한 네트워크 통신 시 효율적인 로그 조회를 위한 요청 ID 할당을 사용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task에 저장할 값을 보관하기 위한 타입을 정의한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;enum MyLocals {
  @TaskLocal static var id: Int!
}&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;@TaskLocal&lt;/b&gt; property wrapper를 사용하여 정적 변수를 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수는 반드시 static이어야 하므로 기본 값을 설정해야 한다. 임의의 기본 값을 사용하는 것 보다 사용하기 전에 초기화를 반드시 해야하는 명시적 언래핑 옵셔널을 사용하는 것이 나은 선택이다.&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;✔️ TaskLocal 값 조회&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;print(MyLocals.id) // nil&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyLocals 타입에 직접 접근하여 확인할 수 있다.&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;p data-ke-size=&quot;size16&quot;&gt;withValue 메서드를 사용하여 값을 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 설정하려는 값과 클로저를 매개변수로 가지고 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;MyLocals.$id.withValue(
  valueDuringOperation: Int?, operation: () throws -&amp;gt; R
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식은 기존 스레드나 DispatchQueue에서 직접 값을 수정할 수 있던 방식과 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 통해 값의 수명을 제어하는 방식으로 설계되어 클로저 실행 동안에만 값이 설정되고, withValue 실행이 끝나면 원래 값으로 복원된다.&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;code&gt;valueDuringOperation&lt;/code&gt; 도 이러한 기능을 암시한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;print(&quot;before:&quot;, MyLocals.id)
MyLocals.$id.withValue(42) {
  print(&quot;withValue:&quot;, MyLocals.id!)
}
print(&quot;after:&quot;, MyLocals.id)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;before: nil
withValue: 42
after: nil&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 설정하고 클로저 내부에서 값을 확인해보면 클로저 내부에서는 값이 변경되지만 클로저 실행이 끝나고 나면 다시 nil로 돌아간다.&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;새로운 Task를 시작하게되면 해당 시점의 모든 &lt;code&gt;Task Local&lt;/code&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;print(&quot;before:&quot;, MyLocals.id)
MyLocals.$id.withValue(42) {
  print(&quot;withValue:&quot;, MyLocals.id!)
  Task {
    try await Task.sleep(nanoseconds: NSEC_PER_SEC)
    Task {
      print(&quot;Task:&quot;, MyLocals.id!)
    }
  }
}
print(&quot;after:&quot;, MyLocals.id)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;before: nil
withValue: 42
after: nil
Task: 42&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에 &lt;b&gt;새롭게 생성된 Task&lt;/b&gt;는 &lt;code&gt;withValue&lt;/code&gt;가 종료된 이후에도 &lt;b&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;이러한 것이 가능한 이유는 새로운 Task를 생성하는 순간 &lt;b&gt;현재 Task의 Task Local 값이 캡쳐&lt;/b&gt;되기 때문이다. 그렇기 때문에 &lt;code&gt;withValue&lt;/code&gt; 클로저가 끝난 후 nil로 초기화 되어도 &lt;b&gt;새로운 Task에서는 값이 유지&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func doSomething() async {
  print(&quot;doSomething:&quot;, MyLocals.id!)
}

print(&quot;before:&quot;, MyLocals.id)
MyLocals.$id.withValue(42) {
  print(&quot;withValue:&quot;, MyLocals.id!)
  Task {
    try await Task.sleep(nanoseconds: NSEC_PER_SEC)
    Task {
      print(&quot;withValue Task:&quot;, MyLocals.id!)
      await doSomething()
    }
  }
}
print(&quot;after:&quot;, MyLocals.id)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;before: nil
withValue: 42
after: nil
Task: 42
doSomething: 42&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task Local은 &lt;b&gt;특정 범위의 생명주기를 기준으로 값을 설정&lt;/b&gt;하도록 설계되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이용하여 이전의 스레드나 DispatchQueue 방식보다 비동기 작업 흐름 속에서 값을 전달할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Task Cooperation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 작업 단위를 동시에 실행하려 할 때 스레드는 &lt;b&gt;Thread Explosion 문제&lt;/b&gt;가 발생하고, 모든 스레드가 CPU 시간을 놓고 경쟁을 하는 상황이 발생했다. 반면 Diapatch Queue는 스레드 풀을 사용하여 스레드의 수를 낮게 유지하여 과부하를 방지했지만, 풀 내의 스레드 수가 제한되어 있기 때문에 너무 많거나 긴 작업이 할당될 경우 작업 처리가 오래걸리게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for _ in 0..&amp;lt;1000 {
  Thread.detachNewThread {
    while true {}
  }
}

Thread.detachNewThread {
  print(&quot;Starting prime thread&quot;)
  nthPrime(50_000)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 위와 같이 1000개의 스레드를 생성하여 무한 루프에 빠지게 하여 바쁘게 유지한 뒤 새로운 스레드를 생성하여 50,000번째 소수를 구하도록 하여 테스트를 해보았다.&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;위와 같은 상황을 Task를 사용하도록 변경하여 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for _ in 0..&amp;lt;workCount {
  Task {
    while true {}
  }
}

Task {
  print(&quot;Starting prime task&quot;)
  nthPrime(primeN)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 실행하면 아무 출력도 나타나지 않게 되는데, 중단점을 통해 스레드 상태를 확인해보면 10개의 스레드가 활성 상태에 있지만 모두 무한 루프에 의해 막혀있는 것을 볼 수 있게 된다.&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;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는 작업을 처리하기 위해 무한히 많은 스레드를 생성할 수 있는 척하지 않고, &lt;b&gt;더 작고 합리적인 수의 스레드를 생성&lt;/b&gt;하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 비동기 작업을 처리할 대 모든 작업이 &lt;b&gt;제한된 스레드를 함께 사용&lt;/b&gt;하도록 한다. 즉, &lt;b&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;✔️ Non-Blocking API&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift는 이 협력을 이루기 위해 Non-Blocking API를 사용한다. 비동기 컨텍스트에서 오랜 시간 동안 블로킹 하지 않는 것이 중요하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 살펴본 await 을 통해 Non-Blocking 작업을 수행할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for n in 0..&amp;lt;1000 {
  Task {
    let (data, _) = try await URLSession.shared.data(
      from: URL(
        string: &quot;&amp;lt;http://ipv4.download.thinkbroadband.com/1MB.zip&amp;gt;&quot;
      )!
    )
    print(n, data.count, Thread.current)
  }
}&lt;/code&gt;&lt;/pre&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;&lt;b&gt;✔️ Task.yield()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 컨텍스트에서 await 을 통해 문제없이 Non-Blocking 작업을 수행할 수 있지만 때로는 어느정도 스레드를 점유해야 할 정도로 무거운 계산 작업을 수행해야 할 경우가 있다.&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;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;await Task.yield()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;yield&lt;/code&gt; 메서드는 비동기 메서드로 &lt;code&gt;await&lt;/code&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;Task.yield()&lt;/b&gt;는 &lt;b&gt;현재 태스크의 스레드를 중단&lt;/b&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;</description>
      <category>iOS/Swift</category>
      <category>concurrency</category>
      <category>ios</category>
      <category>pointfree</category>
      <category>Swift</category>
      <category>Task</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/102</guid>
      <comments>https://iwntberich.tistory.com/102#entry102comment</comments>
      <pubDate>Wed, 18 Dec 2024 16:00:22 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/Point-Free] 동시성 프로그래밍: OperationQueue, CGD와 Combine</title>
      <link>https://iwntberich.tistory.com/101</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;다음 내용은 Point-Free의 Concurrency 강의를 듣고 정리한 내용입니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/episodes/ep191-concurrency-s-present-queues-and-combine&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.pointfree.co/episodes/ep191-concurrency-s-present-queues-and-combine&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733904809439&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;Episode #191: Concurrency's Present: Queues and Combine&quot; data-og-description=&quot;Before developing Swift&amp;rsquo;s modern concurrency tools, Apple improved upon threads with several other abstractions, including operation queues, Grand Central Dispatch, and Combine. Let&amp;rsquo;s see what these newer tools brought to the table.&quot; data-og-host=&quot;www.pointfree.co&quot; data-og-source-url=&quot;https://www.pointfree.co/episodes/ep191-concurrency-s-present-queues-and-combine&quot; data-og-url=&quot;https://www.pointfree.co/episodes/ep191-concurrency-s-present-queues-and-combine&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmIsya/hyXKykbVUM/6nu9pXz2EKFRlAa61vKXGk/img.jpg?width=1440&amp;amp;height=810&amp;amp;face=207_229_1124_577,https://scrap.kakaocdn.net/dn/eBHt7V/hyXKmRyRdo/F9X2kriy6kiwSGzINfO6tk/img.jpg?width=1440&amp;amp;height=810&amp;amp;face=207_229_1124_577&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/episodes/ep191-concurrency-s-present-queues-and-combine&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.pointfree.co/episodes/ep191-concurrency-s-present-queues-and-combine&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmIsya/hyXKykbVUM/6nu9pXz2EKFRlAa61vKXGk/img.jpg?width=1440&amp;amp;height=810&amp;amp;face=207_229_1124_577,https://scrap.kakaocdn.net/dn/eBHt7V/hyXKmRyRdo/F9X2kriy6kiwSGzINfO6tk/img.jpg?width=1440&amp;amp;height=810&amp;amp;face=207_229_1124_577');&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;Episode #191: Concurrency's Present: Queues and Combine&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Before developing Swift&amp;rsquo;s modern concurrency tools, Apple improved upon threads with several other abstractions, including operation queues, Grand Central Dispatch, and Combine. Let&amp;rsquo;s see what these newer tools brought to the table.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.pointfree.co&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;p data-ke-size=&quot;size16&quot;&gt;  이전 내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://iwntberich.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1. 동시성의 과거: Thread&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;Concurrency&amp;rsquo;s Present: Queues and Combine&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Operation Queue&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation Queue는 스레드의 몇가지 문제를 해결하기 위해 도입되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation Queue는 작업을 수행하는 시스템과 실제 작업을 분리한다. 스레드는 직접 스레드를 생성하고 해당 스레드에 수행할 작업을 제공해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation Queue를 생성하고 실행 할 작업을 큐에 추가하면 시스템 내부에서 스레드를 생성하여 작업을 할당하게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// Thread:
let thread = Thread {
    // 스레드에서 수행할 작업
}
thread.start()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// Operation Queue:
let queue = OperationQueue()
queue.addOperaion{
    print(Thread.current)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;&amp;lt;NSThread: 0x100904c20&amp;gt;{number = 2, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기로 실행하기 위해 스레드가 생성되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스레드와의 공통점&lt;/h3&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buDdAs/btsLfmccIj6/CUOhK89wWLSGyA767gcfG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buDdAs/btsLfmccIj6/CUOhK89wWLSGyA767gcfG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buDdAs/btsLfmccIj6/CUOhK89wWLSGyA767gcfG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuDdAs%2FbtsLfmccIj6%2FCUOhK89wWLSGyA767gcfG1%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;536&quot; height=&quot;546&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation Queue가 추가 스레드를 생성하여 작업을 병렬로 처리한다.&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;우선순위&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 스레드와 마찬가지로 작업의 우선순위를 지정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let operation = BlockOperation {  
  print(Thread.current)  
}  
operation.qualityOfService = .background  

queue.addOperation(operation)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위를 지정하기 위해서는 작업을 &lt;code&gt;BlockOperation&lt;/code&gt; 을 통해 클로저로 정의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위는 &lt;b&gt;qualityOfService&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;size18&quot;&gt;&lt;b&gt;취소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드와 동일하게 취소 기능도 지원한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let operation = BlockOperation {  
  Thread.sleep(forTimeInterval: 1)  
  print(Thread.current)  
}  
Thread.sleep(forTimeInterval: 0.1)  
operation.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 해당 코드를 실행하면 작업 내 print가 출력된다. 스레드와 마찬가지로 시스템 내부에서 취소 작업을 처리해주지 않는다. 직접 남은 작업이 취소되었는지 정기적으로 확인하여 중단 여부를 직접 정해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스레드와의 차이점&lt;/h3&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;스레드와 달리 Operation Queue에는 취소를 확인할 수 있는 &lt;b&gt;현재 실행 중인 작업&lt;/b&gt;에 대한 개념이 없다. BlockOperation을 사용하여 작업 객체를 명시적으로 생성한 후에야 취소 여부를 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let operation = BlockOperation()
operation.addExecutionBlock { [unowned operation] in
  Thread.sleep(forTimeInterval: 1)
  guard !operation.isCancelled else {
    print(&quot;Cancelled!&quot;)
    return
  }
  print(Thread.current)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BlockOperation에서 작업을 초기화 한 후 블록 내에서 참조하여 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스레드에서 처럼 sleep 상태를 유지한 후 종료된다.&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;&lt;b&gt;의존성(Dependencies)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation Queue는 의존성 개념을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이용하여 한 작업이 완료된 후 다른 작업을 시작하도록 설정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let queue = OperationQueue()

let operationA = BlockOperation {
  print(&quot;A&quot;)
  Thread.sleep(forTimeInterval: 1)
}
let operationB = BlockOperation {
  print(&quot;B&quot;)
}
operationB.addDependency(operationA)
queue.addOperation(operationA)
queue.addOperation(operationB)

// 출력:
// A
// B&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;operationB.addDependency(operationA)&lt;/code&gt; 를 통해 의존 관계를 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드에서 동일한 방식으로 구현하기 위해서는 다른 스레드가 완료되었는지 확인하기 위해 while루프를 사용하여 지속적으로 확인해야 했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스레드가 가진 문제 해결: Thread Explosion 및 Starvation 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드는 작업을 수행할 때 마다 스레드를 생성하게 되면 수천개의 스레드가 동시에 실행되는 상황이 발생할 수 있다. CPU를 점유하기 위해 서로 경쟁 상태에 들어가게 되며 Thread Starvation이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation Queue는 현재 실행 중인 작업과 완료된 작업을 전체적으로 파악할 수 있기 때문에 효율적으로 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for n in 0..&amp;lt;1000 {
  queue.addOperation { print(n, Thread.current) }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvfXbE/btsLfZ1El75/eR577qcdBEaOMuvtfNDpak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvfXbE/btsLfZ1El75/eR577qcdBEaOMuvtfNDpak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvfXbE/btsLfZ1El75/eR577qcdBEaOMuvtfNDpak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvfXbE%2FbtsLfZ1El75%2FeR577qcdBEaOMuvtfNDpak%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;492&quot; height=&quot;453&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000개의 작업을 요청했지만 60개 정도의 스레드만 생성되었다.&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;h3 data-ke-size=&quot;size23&quot;&gt;Operation Queue의 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ 비동기 작업에서의 Blocking 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation Queue는 비동기 작업에서 non-blocking방식으로 처리할 수 있는 도구가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 작업을 일정시간 기다렸다가 실행하려면 &lt;code&gt;Thread.sleep&lt;/code&gt;을 사용해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;queue.addOperation {
  Thread.sleep(forTimeInterval: 1)
  print(n, Thread.current)
}&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;다른 작업과 자원을 효율적으로 공유하거나 협력할 방법을 제공하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 특정 작업이 대기 중일 경우 자원을 반환하고 다른 작업이 이를 활용하도록 할 방법이 없다. 이로 인해 작업들 간 경쟁이 발생하며 많은 자원을 소모하는 작업이 다른 작업의 수행을 방해할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for n in 0..&amp;lt;1000 {
  queue.addOperation {
    print(n, Thread.current)
    while true {} // CPU를 계속 사용

}
queue.addOperation {
  print(&quot;Starting prime operation&quot;)
  nthPrime(50_000) // 50,000번째 소수 계산
}&lt;/code&gt;&lt;/pre&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;&lt;b&gt;✔️ 취소 제한&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 작업을 취소하더라도 해당 작업에 의존성을 가진 다른 작업은 취소되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;operationB.addDependency(operationA)
operationC.addDependency(operationA)
operationD.addDependency(operationB)
operationD.addDependency(operationC)

operationA.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 사용하였던 의존성 코드를 예시로 사용해볼 때 operationA를 취소하더라도 나머지 B, C, D는 취소되지 않고 그대로 실행된다.&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;✔️ 복잡한 API 설계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Operation Queue는 OOP에서 영감을 받아 설계되었기에 비효율적인 모습이 발견된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let queue = OperationQueue()

let operationA = BlockOperation {
    print(&quot;A&quot;)
    Thread.sleep(forTimeInterval: 1)
}
let operationB = BlockOperation { print(&quot;B&quot;) }
let operationC = BlockOperation { print(&quot;C&quot;) }
let operationD = BlockOperation { print(&quot;D&quot;) }

operationB.addDependency(operationA)
operationC.addDependency(operationA)
operationD.addDependency(operationB)
operationD.addDependency(operationC)

queue.addOperation(operationA)
queue.addOperation(operationB)
queue.addOperation(operationC)
queue.addOperation(operationD)&lt;/code&gt;&lt;/pre&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;모든 작업 선언&lt;/li&gt;
&lt;li&gt;작업간 의존성 설정&lt;/li&gt;
&lt;li&gt;모든 작업을 큐에 추가&lt;/li&gt;
&lt;/ul&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;&lt;b&gt;✔️ Race Condition(데이터 경쟁) 문제&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;스레드가 가진 몇가지 문제를 해결하긴 했지만 모든 문제를 해결하지 못해 GCD나 Swift Concurrecny를 사용해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GCD&lt;b&gt;(Grand Central Dispatch)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD는 스레드 관점에서의 동시성이 아닌 &lt;b&gt;큐를 중심&lt;/b&gt;으로 동시성을 생각하도록 설계되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD는 다음과 같이 생성하고 작업을 추가할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let queue = DispatchQueue(label: &quot;my.queue&quot;)
queue.async {
    print(Thread.current)
}&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;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;&amp;lt;NSThread: 0x101012230&amp;gt;{number = 2, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GCD의 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✔️ Serial Queue&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD는 &lt;b&gt;기본적으로 Serial Queue로 설정&lt;/b&gt;되어 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;queue.async { print(&quot;1&quot;, Thread.current) }
queue.async { print(&quot;2&quot;, Thread.current) }
queue.async { print(&quot;3&quot;, Thread.current) }
queue.async { print(&quot;4&quot;, Thread.current) }
queue.async { print(&quot;5&quot;, Thread.current) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;1 &amp;lt;NSThread: 0x10601fd70&amp;gt;{number = 2, name = (null)}
2 &amp;lt;NSThread: 0x10601fd70&amp;gt;{number = 2, name = (null)}
3 &amp;lt;NSThread: 0x10601fd70&amp;gt;{number = 2, name = (null)}
4 &amp;lt;NSThread: 0x10601fd70&amp;gt;{number = 2, name = (null)}
5 &amp;lt;NSThread: 0x10601fd70&amp;gt;{number = 2, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본으로 Serial Queue이기 때문에 동일한 스레드에서 작업이 순차적으로 실행된다. 이는 동시성 코드의 복잡성을 줄이기 위해 의도된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Concurrent Queue를 사용하려면 명시적으로 설정해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let queue = DispatchQueue(label: &quot;my.queue&quot;, attributes: .concurrent)&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;✔️ Thread explosion 방지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD는 많은 작업을 큐에 추가해도 스레드가 많이 생성되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for n in 0..&amp;lt;workCount {
  queue.async {
    print(n, Thread.current)
  }
}&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;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;0 &amp;lt;NSThread: 0x101108070&amp;gt;{number = 2, name = (null)}
7 &amp;lt;NSThread: 0x101108070&amp;gt;{number = 2, name = (null)}
10 &amp;lt;NSThread: 0x101604330&amp;gt;{number = 10, name = (null)}
5 &amp;lt;NSThread: 0x1014996c0&amp;gt;{number = 7, name = (null)}
&amp;hellip;
929 &amp;lt;NSThread: 0x101498d30&amp;gt;{number = 29, name = (null)}
116 &amp;lt;NSThread: 0x10070f940&amp;gt;{number = 57, name = (null)}
999 &amp;lt;NSThread: 0x101204d20&amp;gt;{number = 30, name = (null)}&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;✔️ Non-Blocking&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD는 특정 시간이 지난 후 작업을 수행하도록 하는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD는 &lt;code&gt;Thread.sleep&lt;/code&gt; 과 같이 스레드를 차단하여 리소스 낭비 문제를 해결하였다. &lt;code&gt;asyncAfter&lt;/code&gt; 를 통해 일정 시간 지난 후 수행하도록 설정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;print(&quot;before scheduling&quot;)
queue.asyncAfter(deadline: .now() + 1) {
    print(&quot;1 second passed&quot;)
}
print(&quot;after scheduling&quot;)

// 출력
// before scheduling
// after scheduling
// 1 second passed&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;✔️ 우선순위(QoS)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD 또한 QoS(Quality of Service)를 사용하여 우선순위를 지정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let queue = DispatchQueue(label: &quot;my.queue&quot;, qos: .background)&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;✔️ DispatchWorkItem&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD에서 작업 단위를 생성하기 위해 &lt;code&gt;DispatchWorkItem&lt;/code&gt; 을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let item = DispatchWorkItem {
    print(Thread.current)
}
queue.async(execute: item)&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;&lt;code&gt;DispatchWorkItem&lt;/code&gt; 으로 생성한 작업 아이템은 실행 도중 작업 취소를 수행할 수 있다. GCD 또한 협력적으로 동작하기 때문에 실행 중 직접 취소 여부를 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 처럼 현재 실행 중인 디스패치 큐를 확인할 수 있는 개념이 존재하지 않아 취소 여부를 확인하려면 해당 큐 자체에 접근해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var item: DispatchWorkItem!
item = DispatchWorkItem {
  defer { item = nil }
  let start = Date()
  defer { print(&quot;Finished in&quot;, Date().timeIntervalSince(start)) }
  Thread.sleep(forTimeInterval: 1)
  guard !item.isCancelled else {
    print(&quot;Cancelled!&quot;)
    return
  }
  print(Thread.current)
}
queue.async(execute: item)

Thread.sleep(forTimeInterval: 0.5)
item.cancel()&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;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;Cancelled!  
Finished in 1.0974069833755493&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.5초 후에 취소를 요청했지만 완료 시간은 1초가 걸렸다. 시스템적으로 강제로 적용되는 것이 아니기 때문에 1초 동안 &lt;code&gt;sleep&lt;/code&gt;을 유지하게 되고, 이 &lt;code&gt;sleep&lt;/code&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;✔️ Specifics&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드의 딕셔너리와 유사하게 &lt;b&gt;Specifics&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;예시로 네트워크 요청을 처리하는 코드를 사용해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func response(for request: URLRequest) -&amp;gt; HTTPURLResponse {
  // 요청을 응답으로 변환하는 작업을 수행

  return .init()
}

let item = DispatchWorkItem {
  response(for: .init(url: URL(string: &quot;https://www.pointfree.co&quot;)!))
}

let requestId = UUID()
let queue = DispatchQueue(label: &quot;request-\(requestId)&quot;)
queue.async(execute: item)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드에서 고유 요청 ID값을 통해 특정 로그를 쉽게 확인할 수 있도록 해보려 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func response(for request: URLRequest) -&amp;gt; HTTPURLResponse {
  let requestId = DispatchQueue.getSpecific(key: requestIdKey)! // 값 검색
  print(requestId, &quot;Making database query&quot;)
  Thread.sleep(forTimeInterval: 0.5)
  print(requestId, &quot;Finished database query&quot;)
  print(requestId, &quot;Making network request&quot;)
  Thread.sleep(forTimeInterval: 0.5)
  print(requestId, &quot;Finished network request&quot;)

  return .init()
}

let item = DispatchWorkItem {
  response(for: .init(url: URL(string: &quot;https://www.pointfree.co&quot;)!))
}

let requestIdKey = DispatchSpecificKey&amp;lt;UUID&amp;gt;() // Specific 키 생성 및 타입 설정
let requestId = UUID()
let queue = DispatchQueue(label: &quot;request-\(requestId)&quot;)
queue.async(execute: item)
queue.setSpecific(key: requestIdKey, value: requestId) // 값 설정&lt;/code&gt;&lt;/pre&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;&lt;b&gt;✔️ Specific의 상속 문제&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;디스패치 큐에는 이러한 문제를 해결하기 위한 &lt;b&gt;Targeting&lt;/b&gt;이라는 도구를 제공한다. 이를 통해 새 큐를 부모 큐에 타겟팅하면 Specific 데이터를 상속받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let queue2 = DispatchQueue(label: &quot;queue2&quot;, target: queue1) // 타겟팅
queue2.setSpecific(key: idKey, value: 1729)
queue2.async {
  print(&quot;queue2&quot;, &quot;id&quot;, DispatchQueue.getSpecific(key: idKey))
  print(&quot;queue2&quot;, &quot;date&quot;, DispatchQueue.getSpecific(key: dateKey))
}&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;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;queue1 id Optional(42)  
queue1 date Optional(2022-05-18 17:36:26 +0000)  
queue2 id Optional(1729)  
queue2 date Optional(2022-05-18 17:36:26 +0000)&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;데이터베이스 쿼리와 네트워크 요청을 병렬로 실행하도록 해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func makeDatabaseQuery() {
  let requestId = DispatchQueue.getSpecific(key: requestIdKey)!
  print(requestId, &quot;Making database query&quot;)
  Thread.sleep(forTimeInterval: 0.5)
  print(requestId, &quot;Finished database query&quot;)
}

func makeNetworkRequest() {
  let requestId = DispatchQueue.getSpecific(key: requestIdKey)!
  print(requestId, &quot;Making network request&quot;)
  Thread.sleep(forTimeInterval: 0.5)
  print(requestId, &quot;Finished network request&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func response(for request: URLRequest) -&amp;gt; HTTPURLResponse {
  let databaseQueue = DispatchQueue(label: &quot;database-query&quot;)
  databaseQueue.async {
    makeDatabaseQuery()
  }

  let networkQueue = DispatchQueue(label: &quot;network-request&quot;)
  networkQueue.async {
    makeNetworkRequest()
  }

  return .init()
}
let item = DispatchWorkItem {
  response(for: .init(url: URL(string: &quot;https://www.pointfree.co&quot;)!))
}

let requestIdKey = DispatchSpecificKey&amp;lt;UUID&amp;gt;() 
let requestId = UUID()
let queue = DispatchQueue(label: &quot;request-\(requestId)&quot;)
queue.async(execute: item)
queue.setSpecific(key: requestIdKey, value: requestId)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;response 메서드에서 각 작업을 실행하기 위한 큐를 생성한다. 비동기적으로 작업하기 위해 &lt;code&gt;async&lt;/code&gt; 를 사용하는데 &lt;code&gt;async&lt;/code&gt; 는 non-blocking작업이기 때문에 각 작업이 실행되기 전에 메서드가 종료된다.&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;&lt;b&gt;✔️ DispatchGroup&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 &lt;b&gt;DispatchGroup&lt;/b&gt;을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchGroup을 통해 여러 작업을 하나의 작업처럼 동작하도록 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func response(for request: URLRequest) -&amp;gt; HTTPURLResponse {
  let databaseQueue = DispatchQueue(label: &quot;database-query&quot;)

  let group = DispatchGroup() // DispatchGroup 생성

    databaseQueue.async(group: group) { // group 부여
      makeDatabaseQuery()
    }

    networkQueue.async(group: group) {
      makeNetworkRequest()
    }
    group.wait()

  return .init()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchGroup을 생성하고 각 비동기 작업을 하나의 그룹 내에서 작업을 처리하게 된다.&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;각 작업에 specific을 부여해줄 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func response(
  for request: URLRequest, queue: DispatchQueue
) -&amp;gt; HTTPURLResponse {
  let group = DispatchGroup()

  let databaseQueue = DispatchQueue(
    label: &quot;database-request&quot;, target: queue
  )
  databaseQueue.async(group: group) {
    makeDatabaseQuery()
  }

  let networkQueue = DispatchQueue(
    label: &quot;network-request&quot;, target: queue
  )
  networkQueue.async(group: group) {
    makeNetworkRequest()
  }

  group.wait()

  return .init()
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let queue = DispatchQueue(
  label: &quot;request-\(requestId)&quot;, attributes: .concurrent
)
response(
  for: .init(url: URL(string: &quot;https://www.pointfree.co&quot;)!),
  queue: queue
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드의 인수로 상속 받을 큐의 정보를 넘기고, 새로운 큐를 생성할 때 target을 부여하도록 하였다. 해당 작업들을 병렬로 실행해야 하기 때문에 부모 큐의 타입을 concurrent로 설정한다.&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 class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;48A7C5DD-5F02-4234-923C-F96315A42214 Making database query
48A7C5DD-5F02-4234-923C-F96315A42214 Making network request
48A7C5DD-5F02-4234-923C-F96315A42214 Finished database query
48A7C5DD-5F02-4234-923C-F96315A42214 Finished network request&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;위의 코드를 더 개선해보자면 현재는 각 요청마다 새로운 concurrent 큐를 생성하는데, 이는 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 시작될 때 하나의 concurrent 큐를 만들고 요청이 들어올 때 마다 해당 큐를 타겟으로 하는 새로운 큐를 만들도록 한다. 타겟팅을 하도록 하여 독립적인 큐를 생성하는 것이 아니라 연결된 큐를 생성하게 된다.&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;이를 통해 sepcific을 공유할 수 있고, 하나의 스레드 풀에서 작업이 분배되어 Thread Explosion 문제를 피할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let serverQueue = DispatchQueue(
  label: &quot;server&quot;, attributes: .concurrent
)
let requestId = UUID()
let requestIdKey = DispatchSpecificKey&amp;lt;UUID&amp;gt;()
let queue = DispatchQueue(
  label: &quot;request-\(requestId)&quot;,
  attributes: .concurrent,
  target: serverQueue
)
queue.setSpecific(key: requestIdKey, value: requestId)
queue.async {
  response(for: .init(url: URL(string: &quot;https://www.pointfree.co&quot;)!))
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GCD의 문제점&lt;/h3&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;기본 큐의 specifics를 상속받을 수 있는 기능은 유용하지만 한계가 있다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;response(
  for: .init(url: URL(string: &quot;https://www.pointfree.co&quot;)!),
  queue: requestQueue
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 함수에 큐를 전달하여 상속 받도록 할 수 있지만 이는 데이터를 여러 계층에 걸쳐 전달하지 않으려는 목적에는 반하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 큐를 생성할 때 부모 큐의 specific을 자동으로 상속받는 구조가 더 바람직하다.&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;p data-ke-size=&quot;size16&quot;&gt;Dispatch Queue는 specific을 상속받을 수는 있지만 &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;&lt;b&gt;✔️ Thread Explosion 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchQueue는 과도한 스레드 생성 문제를 줄이긴 하지만 제대로 사용하지 않으면 여전히 문제가 발생할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for n in 0..&amp;lt;1000 {
  DispatchQueue(label: &quot;queue-\(n)&quot;).async {
    print(Thread.current)
    while true {}
  }
}

Thread.sleep(forTimeInterval: 3)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 코드는 매번 DispatchQueue를 생성하기 때문에 과도한 스레드가 생성된다.&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;&lt;b&gt;✔️ 작업 간 협력 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatchQueue는 다른 작업과 협력할 수 있는 도구를 제공하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 요청이나 데이터베이스 요청과 같은 대기 시간이 긴 작업을 실행하는 동안 다른 작업이 동일한 스레드를 사용할 수 없다. 대기 중인 작업들은 CPU 시간을 두고 경쟁해야 하며, 리소스를 많이 사용하는 작업은 다른 작업의 실행 시간을 빼앗게 된다.&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;✔️ DataRace 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD는 데이터 접근을 동기화 할 수 있는 도구를 제공하지만 이는 이전에 사용한 &lt;code&gt;Lock&lt;/code&gt;과 유사하다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class Counter {
  var count = 0
  func increment() {
    self.count += 1
  }
}
let counter = Counter()

let queue = DispatchQueue(label: &quot;concurrent-queue&quot;, attributes: .concurrent)

for _ in 0..&amp;lt;1000 {
  queue.async {
    counter.increment()
  }
}

Thread.sleep(forTimeInterval: 1)
print(&quot;counter.count&quot;, counter.count)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;출력: counter.count 996&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 실행하면 1000에 도달하지 못하지만 스레드를 사용할 때보다 &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;여전히 data race 문제가 발생할 수 있고, 이러한 문제를 해결하기 위해 GCD는 &amp;ldquo;&lt;b&gt;barrier&amp;rdquo;&lt;/b&gt;라는 도구를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;barrier를 사용하여 큐가 현재 작업을 모두 완료한 후에만 다음 작업 단위를 실행하도록 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class Counter {
  let queue = DispatchQueue(label: &quot;counter&quot;, attributes: .concurrent)
  var count = 0
  func increment() {
    self.queue.sync(flags: .barrier) {
      self.count += 1
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 동일한 1,000이라는 결과를 얻을 수 있지만 이는 시간이 매우 오래 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 환경에서 각각 락 작업을 수행해야 하기 때문에 작업이 느려지고, barrier는 NSLock보다 더 느리기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Combine&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine은 &lt;b&gt;시간에 따라 값의 스트림을 표현할 수 있는 라이브러리&lt;/b&gt;로 설계되었다. Combine은 동시성을 수행하거나 비동기 작업을 위한 라이브러리가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 작업이 가능하지만, DispatchQueue, OperationQueue, Run Loop 등 동시성 도구에 의존하여 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine을 사용하여 우리가 동시성을 다룰 때 발생하는 문제들을 해결하는데 도움을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 단일 값을 방출할 수 있는 Future Publisher를 생성해보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let publisher = Deferred {
  Future&amp;lt;Int, Never&amp;gt; { callback in
    print(Thread.current)
    callback(.success(42))
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future 퍼블리셔는 생성 즉시 실행되기 때문에 구독 시점에 실행되도록 Deferred 퍼블리셔로 감싸야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let cancellable = publisher
  .sink {
    print(&quot;sink&quot;, $0, Thread.current)
  }

_ = cancellable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼블리셔에서 값을 얻기 위해 &lt;code&gt;.sink&lt;/code&gt;를 통해 구독해야하고, 구독을 관리하기 위해 반환되는 Cancellable 객체를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;&amp;lt;_NSMainThread: 0x10610a920&amp;gt;{number = 1, name = main}
publisher 42 &amp;lt;_NSMainThread: 0x10610a920&amp;gt;{number = 1, name = main}&lt;/code&gt;&lt;/pre&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;&lt;b&gt;✔️ zip&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine은 publisher를 시간에 따라 변하는 값의 시퀀스로 생각하고, 이를 다양한 연산자를 사용해서 결합하여 사용할 수 있는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 두 퍼블리셔를 &lt;b&gt;zip&lt;/b&gt;연산자를 사용하여 결합해 하나의 퍼블리셔를 생성하는 코드이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let publisher1 = Deferred {
  Future&amp;lt;Int, Never&amp;gt; { callback in
    print(Thread.current)
    callback(.success(42))
  }
}
let publisher2 = Deferred {
  Future&amp;lt;String, Never&amp;gt; { callback in
    print(Thread.current)
    callback(.success(&quot;Hello world&quot;))
  }
}

let cancellable = publisher1
  .zip(publisher2)
  .sink {
    print(&quot;sink&quot;, $0, Thread.current)
  }

_ = cancellable&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;pre id=&quot;code_1733904734460&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;_NSMainThread: 0x10100c890&amp;gt;{number = 1, name = main}
&amp;lt;_NSMainThread: 0x10100c890&amp;gt;{number = 1, name = main}
sink (42, &quot;Hello world&quot;) &amp;lt;_NSMainThread: 0x10100c890&amp;gt;{number = 1, name = main}&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;이전에는 많은 코드로 두 작업을 결합했던 작업을 Combine을 사용하여 간단하게 수행할 수 있다.&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;✔️ flatMap&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 첫 번째 퍼블리셔가 완료된 후 다음 퍼블리셔를 실행하도록 하고싶다면 &lt;b&gt;flatMap&lt;/b&gt;을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let cancellable = publisher1
  .flatMap { integer in
    Deferred {
      Future&amp;lt;String, Never&amp;gt; { callback in
        print(Thread.current)
        callback(.success(&quot;\(integer)&quot;))
      }
    }
  }
  .zip(publisher2)
  .sink {
    print(&quot;sink&quot;, $0, Thread.current)
  }&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;pre id=&quot;code_1733904641243&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;_NSMainThread: 0x10100c890&amp;gt;{number = 1, name = main}
&amp;lt;_NSMainThread: 0x10100c890&amp;gt;{number = 1, name = main}
&amp;lt;_NSMainThread: 0x10100c890&amp;gt;{number = 1, name = main}
sink (&quot;42&quot;, &quot;Hello world&quot;) &amp;lt;_NSMainThread: 0x10100c890&amp;gt;{number = 1, name = main}&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;퍼블리셔에서 비동기 작업을 수행하려면 기존 동시성 도구를 활용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let publisher1 = Deferred {
  Future&amp;lt;Int, Never&amp;gt; { callback in
    print(Thread.current)
    callback(.success(42))
  }
}
.subscribe(on: DispatchQueue(label: &quot;publisher1&quot;))

let publisher2 = Deferred {
  Future&amp;lt;String, Never&amp;gt; { callback in
    print(Thread.current)
    callback(.success(&quot;Hello&quot;))
  }
}
.subscribe(on: DispatchQueue(label: &quot;publisher2&quot;))

let cancellable = publisher1
  .flatMap { integer in
    Deferred {
      Future&amp;lt;String, Never&amp;gt; { callback in
        print(Thread.current)
        callback(.success(&quot;\(integer)&quot;))
      }
    }
    .subscribe(on: DispatchQueue(label: &quot;publisher3&quot;))
  }
  .zip(publisher2)
  .sink {
    print(&quot;sink&quot;, $0, Thread.current)
  }

Thread.sleep(forTimeInterval: 1)
_ = cancellable&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;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;&amp;lt;NSThread: 0x100711ba0&amp;gt;{number = 2, name = (null)}
&amp;lt;NSThread: 0x100711ba0&amp;gt;{number = 3, name = (null)}
&amp;lt;NSThread: 0x106004830&amp;gt;{number = 4, name = (null)}
sink (&quot;42&quot;, &quot;Hello&quot;) &amp;lt;NSThread: 0x1060052f0&amp;gt;{number = 4, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 publisher가 DispatchQueue에서 작업하도록 설정하였다.&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;&lt;b&gt;✔️ 복잡한 의존성 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 OperationQueue와 DispatchQueue에서 다음과 같은 의존성을 만들었었다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt; A ➡️ B
⬇️    ⬇️
 C ➡️ D&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;이전에는 복잡한 코드를 사용하여야 다음과 같은 구조를 만들 수 있었지만 Combine에서는 간단하게 표현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;a
 .flatMap { a in zip(b(a), c(a)) }
 .flatMap { b, c in d(b, c) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a 퍼블리셔를 시작하여 해당 결과 값을 통해 b와 c 작업을 수행하고, 두 작업의 결과를 활용하여 d 작업을 수행한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Swift의 새로운 동시성 도구&lt;/b&gt;인 &lt;b&gt;async/await&lt;/b&gt;을 사용한다면 더 간단하게 표현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let a = await f()
async let b = g(a)
async let c = h(a)
let d = await i(b, c)&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  다음 내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://iwntberich.tistory.com/102&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;3. 동시성프로그래밍: Task&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS/Swift</category>
      <category>Combine</category>
      <category>concurrency</category>
      <category>GCD</category>
      <category>ios</category>
      <category>OperationQueue</category>
      <category>pointfree</category>
      <category>Swift</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/101</guid>
      <comments>https://iwntberich.tistory.com/101#entry101comment</comments>
      <pubDate>Wed, 11 Dec 2024 17:13:34 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/Point-Free] 동시성 프로그래밍의 과거 - 스레드</title>
      <link>https://iwntberich.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다음 내용은 Point-Free의 Concurrency 강의를 듣고 정리한 내용입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/episodes/ep190-concurrency-s-past-threads&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.pointfree.co/episodes/ep190-concurrency-s-past-threads&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732537346802&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;Episode #190: Concurrency's Past: Threads&quot; data-og-description=&quot;To better understand Swift&amp;rsquo;s concurrency tools, let&amp;rsquo;s first look to the past, starting with threads. Threads are a tool most developers don&amp;rsquo;t reach for these days, but are important to understand, and the way they solve problems reverberate even in t&quot; data-og-host=&quot;www.pointfree.co&quot; data-og-source-url=&quot;https://www.pointfree.co/episodes/ep190-concurrency-s-past-threads&quot; data-og-url=&quot;https://www.pointfree.co/episodes/ep190-concurrency-s-past-threads&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/duwJZJ/hyXDbbsZsV/6iAupN9vO6EwH7r1RH1AMk/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=309_258_1593_738,https://scrap.kakaocdn.net/dn/bf1eo8/hyXDgw3Eg6/CPjLGK1AkJWVTEDtCBxUGK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=309_258_1593_738&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/episodes/ep190-concurrency-s-past-threads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.pointfree.co/episodes/ep190-concurrency-s-past-threads&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/duwJZJ/hyXDbbsZsV/6iAupN9vO6EwH7r1RH1AMk/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=309_258_1593_738,https://scrap.kakaocdn.net/dn/bf1eo8/hyXDgw3Eg6/CPjLGK1AkJWVTEDtCBxUGK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=309_258_1593_738');&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;Episode #190: Concurrency's Past: Threads&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;To better understand Swift&amp;rsquo;s concurrency tools, let&amp;rsquo;s first look to the past, starting with threads. Threads are a tool most developers don&amp;rsquo;t reach for these days, but are important to understand, and the way they solve problems reverberate even in t&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.pointfree.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thread Basics&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Operation Queue나 GCD 등 스레드를 고려하지 않고 비동기 코드를 작성하도록 발전해왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;동시성 코드를 잘 이해하기 위해서는 동시성 코드의 시작인 Thread에 대해 공부해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;운영체제는 수십, 수백개의 논리적 스레드를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Thread 는 클래스타입으로 정의되어있다. POSIX 스레드를 기반으로 만들어져서 사용자가 해당 내용에 대해 알지 못해도 스레드를 쉽게 다룰 수 있도록 애플에서 Thread 클래스를 제공한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Thread.detachNewThread{
    Thread.sleep(forTimeInterval: 1)    
    print(Thread.current)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 내용을 실행시키면 출력되지 않고 프로그램이 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;detachNewThread&lt;/code&gt; 를 통해 메인 스레드가 아닌 새로운 스레드를 생성하게 되었고, 그 내부에서 동작하기 때문에 &lt;b&gt;sleep&lt;/b&gt; 을 기다리지 않고 프로그램이 종료되는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그렇기 때문에&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Thread.detachNewThread{
    Thread.sleep(forTimeInterval: 1)    
    print(Thread.current) // 1
}
Thread.sleep(forTimeIntervar: 1.1)
print(Thread.current) // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 코드를 실행시킨 후 현재 실행되고있는 범위의 스레드 정보를 출력시키면 서로 다른 정보를 출력하게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;&amp;lt;NSThread: 0x106404d00&amp;gt;{number = 2, name = (null)} // 1
&amp;lt;_NSMainThread: 0x10600aa90&amp;gt;{number = 1, name = main} // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;스레드의 순서는 보장되지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;Thread.detachNewThread{
    print(&quot;1&quot;, Thread.current)
}
Thread.detachNewThread{
    print(&quot;2&quot;, Thread.current)
}
Thread.detachNewThread{
    print(&quot;3&quot;, Thread.current)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;여러 개의 스레드를 생성할 때, 스레드의 생성 순서대로 실행되지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;운영체제는 스레드에 실행시간을 할당하는 복잡한 로직을 처리하기 때문에 실행 순서를 예측할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;detachNewThread&lt;/code&gt;를 통해 다른 스레드를 방해하지 않고 작업을 수행할 수 있다. 이론적으로는 컴퓨터에 유한한 수의 코어가 존재한다. 하지만 운영체제 내부적으로 복잡한 작업을 처리하여 여러 스레드가 각자 작업을 수행하고 다른 스레드로 넘어가도록 하여 여러 작업이 교대로 이루어질 수 있도록 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Priority and Cancellation&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우선순위&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Thread의 초기화 함수로 스레드를 생성하여 스레드의 우선순위를 설정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let thread = Thread {
  Thread.sleep(forTimeInterval: 1)
  print(Thread.current)
}
thread.start()
thread.threadPriority = 0.75&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;우선순위는 0에서 1 사이의 double 값으로, 운영체제가 해당 스레드를 더 빨리 실행하도록 권장할 수 있지만 보장 되진 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;취소&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;thread.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;cancel()&lt;/code&gt; 을 호출하여 &lt;b&gt;실행 중인 스레드를 중지 요청&lt;/b&gt;을 보낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;cancel()&lt;/code&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;pre class=&quot;lasso&quot;&gt;&lt;code&gt;let thread = Thread {
  Thread.sleep(forTimeInterval: 1)
  guard !Thread.current.isCancelled else {
    print(&quot;Cancelled&quot;)
    return
  }
  print(Thread.current)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;Thread.current.isCancelled&lt;/code&gt; 플래그를 사용하여 스레드 내부에 플래그를 확인하는 코드를 넣어주는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;플래그를 자주 확인하여 불필요한 작업을 빠르게 종료할 수 있도록 하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;다만, 취소 매커니즘이 다른 스레드 작업들과 완전히 결합되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;Thread.sleep()&lt;/code&gt; 이 동작하고 있을 동안 취소 플래그를 확인하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let thread = Thread {
  Thread.sleep(forTimeInterval: 1)
  print(Thread.current)
}
thread.start()
Thread.sleep(forTimeInterval: 0.01)
thread.cancel()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;Thread.sleep&lt;/code&gt; 에서 취소된 스레드를 인지하지 못하고 취소가 되더라도 지정된 시간이 지날 때 까지 기다리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이러한 이유는 아마도 CPU에서 오버헤드를 피하기 위한 의도로 보여진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;우선순위와 취소는 스레드에서 &lt;b&gt;Cooperative(협조)&lt;/b&gt;의 특성을 보여주는 기능이다. 스레드에서 낮은 우선순위를 설정하여 다른 스레드들이 CPU에서 더 많은 작업을 할 수 있도록 하고, 스레드 취소를 감지하게 되면 스레드의 작업을 중단하여 불필요한 작업을 빠르게 끝낼 수 있도록 할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Problems: Coordination&lt;/h2&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;size14&quot;&gt;Thread Dictionary는 말 그대로 딕셔너리 타입이기 때문에 안정성 부분에서 떨어지게 된다. 키 이름을 잘못 입력했을 경우 컴파일러가 해당 문제를 잡을 수 없기 때문에 &lt;b&gt;런타임 시 문제&lt;/b&gt;가 발생할 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;스레드 딕셔너리를 통해서 데이터를 쉽게 전달할 수 있지만 문제는 &lt;b&gt;단일 스레드에 한해서만 동작&lt;/b&gt;한다는 것이다. 기존 스레드 내부에서 새로 스레드를 생성하더라도 기존 딕셔너리 데이터를 전달할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;예시) 병렬 처리&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func makeDatabaseQuery() {
  let requestId = Thread.current.threadDictionary[&quot;requestId&quot;] as! UUID
  print(requestId, &quot;Making database query&quot;)
  Thread.sleep(forTimeInterval: 0.5)
  print(requestId, &quot;Finished database query&quot;)
}

func makeNetworkRequest() {
  let requestId = Thread.current.threadDictionary[&quot;requestId&quot;] as! UUID
  print(requestId, &quot;Making network request&quot;)
  Thread.sleep(forTimeInterval: 0.5)
  print(requestId, &quot;Finished network request&quot;)
}

func response(for request: URLRequest) -&amp;gt; HTTPURLResponse {
  let start = Date()
  let requestId = Thread.current.threadDictionary[&quot;requestId&quot;] as! UUID

  let databaseQueryThread = Thread { makeDatabaseQuery() }
  databaseQueryThread.start()

  let networkRequestThread = Thread { makeNetworkRequest() }
  networkRequestThread.start()

  // TODO: join threads somehow

  print(requestId, &quot;Completed in&quot;, Date().timeIntervalSince(start))
  return .init()
}

// 새 스레드에서 네트워크 요청
let thread = Thread {
  response(for: .init(url: .init(string: &quot;&amp;lt;https://www.pointfree.co&amp;gt;&quot;)!))
}
thread.threadDictionary[&quot;requestId&quot;] = UUID()
thread.start()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;makeDatabaseQuery()&lt;/code&gt; 와 &lt;code&gt;makeNetworkRequest()&lt;/code&gt; 가 내부에서 생성된 스레드를 통해서 병렬적으로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 코드는 오류가 발생하는데 그 이유는 외부에서 생성된 스레드 딕셔너리의 데이터가 내부에서 생성된 스레드에 전달이 되지 않아 requestId값을 찾는데에 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;rarr; 내부에서 스레드를 생성하였지만 &lt;b&gt;스레드는 자식 스레드의 개념이 없기 때문에&lt;/b&gt; 별도의 스레드가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 코드가 원하는대로 동작하려면 &lt;b&gt;새로 생성한 딕셔너리에 데이터를 복사&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let databaseQueryThread = Thread { makeDatabaseQuery() }
databaseQueryThread.threadDictionary.addEntries(
  from: Thread.current.threadDictionary as! [AnyHashable: Any]
)
databaseQueryThread.start()&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;size14&quot;&gt;위의 예시 코드에서 &lt;code&gt;databaseQueryThread()&lt;/code&gt; 와 &lt;code&gt;networkRequestThread()&lt;/code&gt; 는 서로 다른 비동기 작업을 수행하게 된다. 그렇기 때문에 두 작업이 끝나기 전에 함수가 종료된다. 두 작업의 완료를 기다리는 매커니즘이 스레드에는 존재하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;while !databaseQueryThread.isFinished || !networkRequestThread.isFinished {
  Thread.sleep(forTimeInterval: 0.1)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;해당 코드처럼 임시로 while루프를 통해 스레드 종료 여부를 판단하는 방법 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 방식은 &lt;b&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 스레드 딕셔너리 문제와 유사하게 스레드 취소 작업 또한 내부 스레드에 전달되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let thread = Thread {
    print(&quot;Outer thread started&quot;)
    Thread.sleep(forTimeInterval: 1) // 1초 동안 대기
    print(&quot;Outer thread is cancelled:&quot;, Thread.current.isCancelled)

    // 외부 스레드에서 새 스레드를 만듦
    let innerThread = Thread {
        print(&quot;Inner thread started&quot;)
        print(&quot;Inner thread is cancelled:&quot;, Thread.current.isCancelled)
        Thread.sleep(forTimeInterval: 2)
        print(&quot;Inner thread finished&quot;)
    }
    innerThread.start() // 내부 스레드 실행

    guard !Thread.current.isCancelled else {
        print(&quot;Outer thread was cancelled&quot;)
        return
    }
    print(&quot;Outer thread finished&quot;)
}

thread.start()
Thread.sleep(forTimeInterval: 0.5) // 0.5초 대기 후
thread.cancel() // 외부 스레드를 취소함

// 출력:
// Outer thread started
// Inner thread started
// Outer thread is cancelled: true
// Inner threa is cancelled: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 코드에서 스레드를 실행하게 된다면 외부 스레드(Outer thread)는 &lt;code&gt;thread.cancel()&lt;/code&gt; 로 인해 종료되지만 내부에서 생성된 스레드(Inner thread)는 종료되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;즉, 외부 스레드를 취소하더라도 &lt;b&gt;취소 상태가 내부에 전달되지 않아&lt;/b&gt; 내부 스레드는 계속 작업을 진행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위와 같은 문제들은 비동기 작업을 효율적으로 관리하는 데에 불편함을 야기한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Problems: Expensiveness&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;만약 우리가 처리해야 할 내용이 많을 경우 스레드의 비용 측면에서 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;스레드를 생성하는데에 많은 비용이 든다. 스레드를 생성하게 되면 운영체제가 작업을 빨리 처리할 수 있도록 스케줄링하지만, 그러기 위해 다른 스레드와 경쟁을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;만약 1000개의 스레드를 만들게 되면 많은 스레드가 서로 경쟁해서 운영체제가 시간을 적절히 할당하기 어려워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그렇기 때문에 작업이 느려지고 시스템이 비효율적으로 작동하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;각 스레드는 고유한 호출 스택(작업을 처리하기 위해 필요한 메모리 공간)을 가지고 있으며, 이 스택의 크기는 0.5MB이다. 그렇기 때문에 1000개의 스레드를 생성하면 호출 스택만으로 500MB의 메모리가 필요하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;size14&quot;&gt;많은 스레드가 생성되어 각 스레드가 CPU를 사용하는 시간이 줄어들게 된다면 각 스레드가 작업을 처리할 수 있는 시간이 줄어들게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;예를 들어, 1,000개의 스레드를 생성한 채로 다른 스레드에서 50,000번째 소수 구하기 작업을 수행한다고 해보자. 다른 경쟁 스레드가 없는 상황이라면 50,000번째 소수를 계산하는데는 0.025초가 걸리지만 경쟁 스레드가 1,000개가 있는 경우 2.93초로 100배 이상 처리 속도가 느려진다.&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;✔️ Non-blocking 작업&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;non-blocking 작업은 비동기 작업을 수행할 때 CPU가 실제로 작업을 수행하고 있지 않은 대기 시간에 다른 작업을 처리할 수 있도록 처리하는 방식을 의미한다. 예를 들어, 어떠한 작업을 지연시키거나, 네트워크 요청 시 서버로부터 데이터를 받을 때 까지 기다리는 경우 CPU는 처리할 작업이 많지 않기 때문에 non-blocking이 필요하게 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이러한 경우 다른 스레드가 작업을 처리할 수 있도록 잠시 스레드를 포기했다가 준비가 되었을 때 다시 작업을 처리할 수 있어야 하지만 스레드에서는 이러한 처리를 하기 어렵다. 스레드는 CPU와 밀접하게 연결되어있고, CPU 코어에서는 연속적으로 작업을 처리하도록 설계되어 있기 때문에 잠시 멈추거나 다른 작업을 위해 대기하는 것이 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;스레드에서 대기를 하기 위해 &lt;code&gt;Thread.sleep&lt;/code&gt; 을 사용해볼 수 있는데 해당 방식은 매우 &lt;b&gt;비효율적&lt;/b&gt;이다. 스레드 리소스를 사용하고 있지만, 실제로는 작업을 하고있지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;&lt;b&gt;✔️ Thread pool&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;스레드 기아 문제&lt;/b&gt;나 &lt;b&gt;스레드 과잉 생성 문제&lt;/b&gt;를 해결하기 위해 &lt;b&gt;스레드 풀&lt;/b&gt;을 만드는 방식을 주로 사용하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- 스레드 기아 문제: 한정된 수의 스레드가 많은 작업을 처리하려 할 때, 일부 스레드가 오래 기다려야 하는 경우. 즉, 작업이 완료되지 않은 채로 계속 대기하는 상황&lt;br /&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;- 스레드 과잉 생성 문제: 너무 많은 스레드가 생성되어 CPU 사용 시간 할당이 어려워 성능이 저하되고, 비효율적으로 작동하는 문제.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;스레드 풀은 일정 수의 스레드를 미리 생성하고 관리하는 방식이다. 스레드가 필요할 때 새로 생성하는 대신 스레드 풀에 준비되어 있는 스레드를 요청하여 작업을 처리하게 된다. 스레드가 모두 사용 중일 경우에는 하나가 완료될 때 까지 기다리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;여전히 타이머나 네트워크 요청 등의 대기 상태의 경우 CPU에서 다른 작업을 처리할 수 없는 상태가 된다.&lt;/li&gt;
&lt;li&gt;국소적 해결책&lt;/li&gt;
&lt;li&gt;스레드 풀은 스레드를 과도하게 생성되지 않도록 할 수 있지만 직접 관리하는 코드 내에서만 가능하다. 외부 라이브러리나 프레임워크에서 자체적으로 스레드 풀을 사용하고 있을 수 있어 독립적인 스레드 풀이 여러 개 존재할 수 있다. 이는 결국 스레드 과잉 생성 문제를 다시 일으킬 수 있다.&lt;/li&gt;
&lt;li&gt;협력적 동시성(cooperative concurrency)의 부족&lt;/li&gt;
&lt;li&gt;협력적 동시성은 여러 스레드가 자원을 공유하며 효율적으로 사용하는 방식을 의미하는데 스레드는 이러한 기능을 기본적으로 제공하지 않는다. 사용하기 위해서는 직접 만들어야 하며 글로벌한 협력적 솔루션을 제공하기 어렵다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Problems: Data Races&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;스레드를 생성하여 작업을 하게되면 &lt;b&gt;Race Condition&lt;/b&gt;이라는 문제를 마주하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Race Condition: 여러 스레드가 동일한 데이터를 동시에 읽거나 수정할 때 발생하는 문제&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;&lt;b&gt;✔️ 데이터레이스 발생 원인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;다음과 같은 클래스에서 가변 상태의 값을 여러 스레드에서 읽거나 수정하려고 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class Counter {
  var count = 0
}
let counter = Counter()&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;size14&quot;&gt;그 후 1000개의 스레드를 생성하여 값을 증가시키게 되면&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;for _ in 0..&amp;lt;1_000 {
  Thread.detachNewThread {
    Thread.sleep(forTimeInterval: 0.01)
    counter.count += 1
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;출력 값은 1000보다 작은 값이 실행할 때 마다 다르게 나올 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이러한 결과가 나오는 이유는 값을 변경하는 작업이 &lt;b&gt;단일 원자적 연산이 아니기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;counter.count += 1&lt;/code&gt; 은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 count값 가져오기&lt;/li&gt;
&lt;li&gt;값 증가시키기&lt;/li&gt;
&lt;li&gt;값을 count에 할당하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이렇게 세 단계로 동작하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;우리가 원하는 동작 방식은 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;스레드 1: var count1 = counter.count  // 0
스레드 1: count1 += 1                 // 1
스레드 1: counter.count = count1      // 1
스레드 2: var count2 = counter.count  // 1
스레드 2: count2 += 1                 // 2
스레드 2: counter.count = count2      // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;하지만 실제로는 순서가 아래와 같이 섞여 실행될 가능성이 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;스레드 1: var count1 = counter.count  // 0
스레드 2: var count2 = counter.count  // 0
스레드 1: count1 += 1                 // 1
스레드 2: count2 += 1                 // 1
스레드 1: counter.count = count1      // 1
스레드 2: counter.count = count2      // 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;스레드 2는 count의 값을 가져올 때 1이 아닌 0의 값을 가져오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이러한 data race 문제를 해결하기 위해서는 접근을 &lt;b&gt;동기화&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;값을 변경하기 시작할 때 다른 스레드는 접근을 하지 못하고 수정이 완료된 후에 접근할 수 있도록 락(lock)을 사용해야 한다.&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;✔️ 락(lock)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;락은 코드의 일부가 실행되는 동안 다른 스레드들이 해당 부분을 실행하지 못하도록 막는 기능을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;NSLock()&lt;/code&gt; 을 이용하여 변경 작업 전 후에 락을 걸고 푸는 작업을 추가하는 메서드를 구현하여 해결할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class Counter {
  let lock = NSLock()
  private(set) var count = 0

  func increment() {
    self.lock.lock()
    defer { self.lock.unlock() }
    self.count += 1
  }
}

for _ in 0..&amp;lt;1_000 {
  Thread.detachNewThread {
    Thread.sleep(forTimeInterval: 0.01)
    counter.increment() // 증가 메서드 호출
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이렇게 실행하면 항상 일관된 1,000이라는 값을 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 코드는 값을 변경하기 위해 락을 걸고 푸는 메서드를 따로 만들어야 하기 때문에 클로저를 통해 modify 메서드를 만들어 볼 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func modify(work: (Counter) -&amp;gt; Void) {
  self.lock.lock()
  defer { self.lock.unlock() }
  work(self)
}
counter.modify {
  $0.count += 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위와 같은 코드도 좋지만 다음과 같이 getter, setter를 사용하는 것이 변경하려는 데이터를 더 명시적으로 표기할 수 있을 것이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;private var _count = 0
var count: Int {
  get {
    self.lock.lock()
    defer { self.lock.unlock() }
    return self._count
  }
  set {
    self.lock.lock()
    defer { self.lock.unlock() }
    self._count = newValue
  }
}

for _ in 0..&amp;lt;1_000 {
  Thread.detachNewThread {
    Thread.sleep(forTimeInterval: 0.01)
      counter.count += 1
  }
}
// count = 539&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;하지만 위의 코드를 실행하면 또 다시 1,000이하의 값이 나오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 코드는 값을 읽고 쓰는 get과 set 부분에만 락이 걸리게 되며 전체 트랜잭션에는 락이 걸리지 않기 때문에 발생하는 문제이다.&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;✔️ read-modify&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(비공식 지원 기능?)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;read-modify는 동시성 관련 작업을 처리하는 기능으로, 속성의 읽기 및 수정 작업을 안전하게 처리할 수 있는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 코드를 read-modify를 사용하여 다음과 같이 수정해 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var count: Int {
  _read {
    self.lock.lock()
    defer { self.lock.unlock() }
    yield self._count
  }
  _modify {
    self.lock.lock()
    defer { self.lock.unlock() }
    yield &amp;amp;self._count
  }
}

counter.count += 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;해당 코드는 정확히 1,000의 값을 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;code&gt;counter.count += 1&lt;/code&gt; 과 같은 연산은 단일 트랜잭션으로 처리하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;하지만, read-modify에서도 문제가 발생할 가능성이 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;counter.count += 1 + counter.count / 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;counter.count를 여러 번 참조하는 경우 문제가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;읽기와 쓰기에 별도로 락이 걸리게 되면서 읽기와 쓰기 사이에 다른 스레드가 개입할 수 있게 된다. 그렇게 되면 해당 연산을 단일 트랜잭션으로 묶지 못하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위의 문제를 해결하기 위해선 결국 위에서 사용하였던 modify 메서드를 사용하여 연산 자체를 단일 트랜잭션을 묶어야 한다. 동기화 문제를 완전히 해결하기 위해서는 결국 상태를 변경하는 전용 메서드를 활용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이러한 문제점들은 멀티스레딩과 데이터레이스 문제의 까다로움을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;락은 스레드와 분리되어 있는 도구라는 문제점이 있다. Swift의 새로운 동시성 도구는 스레드와 밀접하게 통합되어 있어서 더 쉽게 문제를 해결할 수 있다.&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;&lt;b&gt;Thread의 한계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size14&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;/li&gt;
&lt;li&gt;스레드의 수가 과도하게 증가될 수 있다.&lt;/li&gt;
&lt;li&gt;코드의 가독성이 떨어진다.&lt;/li&gt;
&lt;li&gt;스레드 간 동기화를 위한 툴이 제한적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  다음내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://iwntberich.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2. 동시성프로그래밍: OperationQueue, GCD, Combine&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS/Swift</category>
      <category>concurrency</category>
      <category>ios</category>
      <category>pointfree</category>
      <category>Swift</category>
      <category>thread</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/100</guid>
      <comments>https://iwntberich.tistory.com/100#entry100comment</comments>
      <pubDate>Mon, 25 Nov 2024 21:37:08 +0900</pubDate>
    </item>
    <item>
      <title>[RxSwift] Rx 를 사용하여시스템 권한 요청받기 (카메라, 앨범, 알림)</title>
      <link>https://iwntberich.tistory.com/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 시스템 권한 얼럿을 RxSwift를 이용하여 띄워보자!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_5283.PNG&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mOzXi/btsKyfD4v8M/7F1BVKYuokCKoP9vmDhXW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mOzXi/btsKyfD4v8M/7F1BVKYuokCKoP9vmDhXW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mOzXi/btsKyfD4v8M/7F1BVKYuokCKoP9vmDhXW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmOzXi%2FbtsKyfD4v8M%2F7F1BVKYuokCKoP9vmDhXW0%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;334&quot; height=&quot;2532&quot; data-filename=&quot;IMG_5283.PNG&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;/&gt;&lt;/span&gt;&lt;/figure&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;PermissionManager 라는 이름을 가진 싱글톤 클래스를 만들어 권한 확인 및 요청 관련 메서드를 관리&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;public final class PermissionManager {

  public static let shared = PermissionManager()
  private init() { }
  private let disposeBag = DisposeBag()

  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;권한 요청&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RxSwift를 사용하여 구현하려고 하기 때문에 Observable 타입을 리턴하도록 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리턴되는 Bool타입의 데이터는 해당 권한을 허용했는지 여부이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알림 권한 요청&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림 권한 얼럿의 메세지는 시스템 내부에 정해져있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림에 대한 얼럿과 소리 뱃지 등의 옵션만 설정하면 얼럿 메세지가 만들어진다!&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func requestNotificationPermission() -&amp;gt; Observable&amp;lt;Bool&amp;gt; {
  return Observable&amp;lt;Bool&amp;gt;.create { observable in
    UNUserNotificationCenter.current()
      .requestAuthorization(options: [.alert, .sound, .badge]) { isAllow, _ in
        observable.onNext(isAllow)
        observable.onCompleted()
      }
    return Disposables.create()
  }
}&lt;/code&gt;&lt;/pre&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;pre class=&quot;less&quot;&gt;&lt;code&gt;button.rx.tap
  .flatMap {
      PermissionManager().checkPermission(for: .camera)
  }
  .subscribe(with: self) { owner, status in
      // 권한 상태에 따라 적절한 액션 구현
  }
  .disposed(by: disposBag())&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카메라, 앨범 권한 요청&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;info.plist에서 권한 메세지 설정이 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;privacy - NSCameraUsageDescription&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;privacy - Photo Library Description&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1606&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c03Ee4/btsKvLkHTGB/sRP3T4ZFgpWBUmC9lNZUp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c03Ee4/btsKvLkHTGB/sRP3T4ZFgpWBUmC9lNZUp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c03Ee4/btsKvLkHTGB/sRP3T4ZFgpWBUmC9lNZUp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc03Ee4%2FbtsKvLkHTGB%2FsRP3T4ZFgpWBUmC9lNZUp1%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;1606&quot; height=&quot;252&quot; data-origin-width=&quot;1606&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;import AVFoundation
import Photos

// ...

func requestCameraPermission() -&amp;gt; Observable&amp;lt;Bool&amp;gt; {
  Observable&amp;lt;Bool&amp;gt;.create { observable in
    AVCaptureDevice.requestAccess(for: .video) { granted in
      observable.onNext(granted)
      observable.onCompleted()
    }
    return Disposables.create()
  }
}

func requestPhotoPermission() -&amp;gt; Observable&amp;lt;Bool&amp;gt; {
  Observable&amp;lt;Bool&amp;gt;.create { observable in
    PHPhotoLibrary.requestAuthorization(for: .addOnly) { state in
      if state == .authorized {
        observable.onNext(true)
        observable.onCompleted()
      } else {
        observable.onNext(false)
        observable.onCompleted()
      }
    }
    return Disposables.create()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모든 권한 한 번에 요청하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 처음 진입했다면 처음에 필요한 권한을 모두 요청받는게 필요할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;permissionmanager.gif&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7lOKE/btsKw7z2Izy/yAYyxFvRMnjNSfdzxkGu1K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7lOKE/btsKw7z2Izy/yAYyxFvRMnjNSfdzxkGu1K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7lOKE/btsKw7z2Izy/yAYyxFvRMnjNSfdzxkGu1K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b7lOKE/btsKw7z2Izy/yAYyxFvRMnjNSfdzxkGu1K/img.gif&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;320&quot; height=&quot;692&quot; data-filename=&quot;permissionmanager.gif&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 권한을 하나씩 요청하는 메서드를 구현해보자&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func requestAllPermission() -&amp;gt; Observable&amp;lt;Void&amp;gt; {
  return requestNotificationPermission()
    .map { _ in Void() }
    .flatMap { _ in
      self.requestCameraPermission()
        .map { _ in Void()}
    }
    .flatMap {
      self.requestPhotoPermission()
        .map { _ in Void() }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림-카메라-앨범 순서로 권한 요청 얼럿을 띄울 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;권한 상태 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.notDetermined: 권한 요청 받은 적 없음을 의미&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;&lt;b&gt;알림권한&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// 알림 권한
var notificationPermission: Observable&amp;lt;Bool&amp;gt; {
  return Observable&amp;lt;Bool&amp;gt;.create { observable in
    UNUserNotificationCenter.current().getNotificationSettings { settings in
      switch settings.authorizationStatus {
      case .notDetermined:
        self.requestNotificationPermission()
          .subscribe(onNext: { isAllowed in
            observable.onNext(isAllowed)
            observable.onCompleted()
          })
          .disposed(by: self.disposeBag)
      case .authorized:
        observable.onNext(true)
        observable.onCompleted()
      default:
        observable.onNext(false)
        observable.onCompleted()
      }
    }
    return Disposables.create()
  }
}&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var cameraPermission: Observable&amp;lt;Bool&amp;gt; {
  return Observable&amp;lt;Bool&amp;gt;.create { observable in
    let status = AVCaptureDevice.authorizationStatus(for: .video)
    switch status {
    case .notDetermined:
      self.requestCameraPermission()
        .subscribe { isAllowed in
          observable.onNext(isAllowed)
          observable.onCompleted()
        }
        .disposed(by: self.disposeBag)
    case .authorized:
      observable.onNext(true)
      observable.onCompleted()
    default:
      observable.onNext(false)
      observable.onCompleted()
    }

    return Disposables.create()
  }
}&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;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// 앨범 권한  
var photoPermission: Observable&amp;lt;Bool&amp;gt; {
  return Observable&amp;lt;Bool&amp;gt;.create { observable in
    let status = PHPhotoLibrary.authorizationStatus(for: .addOnly)
    switch status {
    case .notDetermined:
      self.requestPhotoPermission()
        .subscribe { isAllowed in
          observable.onNext(isAllowed)
          observable.onCompleted()
        }
        .disposed(by: self.disposeBag)
    case .authorized:
      observable.onNext(true)
      observable.onCompleted()
    default:
      observable.onNext(false)
      observable.onCompleted()
    }

    return Disposables.create()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;권한 상태 확인을 보다 깔끔하게 사용하기&lt;/h3&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;&lt;b&gt;권한 타입 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum PermissionType {
  case notification, camera, photo
}&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;파라미터로 확인하고자 하는 권한 타입을 넣어 확인할 수 있다!&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func checkPermission(for type: PermissionType) -&amp;gt; Observable&amp;lt;Bool&amp;gt; {
  switch type {
  case .notification:
    return notificationPermission
  case .camera:
    return cameraPermission
  case .photo:
    return photoPermission
  }
}&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;pre class=&quot;less&quot;&gt;&lt;code&gt;button.rx.tap
    .flatMap {
        PermissionManager().checkPermission(for: .camera)
    }
    .subscribe(with: self) { owner, status in
        ...
    }
    .disposed(by: disposBag)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>iOS/RxSwift&amp;amp;Combine</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/99</guid>
      <comments>https://iwntberich.tistory.com/99#entry99comment</comments>
      <pubDate>Wed, 6 Nov 2024 14:42:31 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/Swift] 인앱에서 앱스토어 리뷰 팝업 띄우기</title>
      <link>https://iwntberich.tistory.com/98</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;인앱리뷰&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;최대 3번 노출 가능&lt;/h3&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;그렇기 때문에 시스템 내부적으로 365일 내에 최대 3번만 노출되도록 제한이 되어있다.&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;단, 시스템에서 제어하는 경우는 앱스토어에 릴리즈 된 앱의 경우에만 해당된다. 디버그 모드에서는 리뷰 팝업이 설계한 대로 무조건 노출되고, 테스트플라이트에서는 노출되지 않는다.&lt;/p&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;리뷰 요청 팝업을 띄울 때는 적절한 타이밍을 찾아야한다.&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;사용자가 앱 내에서 작업을 수행하는 과정 중에 노출시키는 것 또한 적절하지 않은 타이밍이다. 사용자의 앱 사용을 방해하면 안된다.&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;사용자가 충분히 앱을 사용하고 좋은 경험이 쌓였을 시점에 노출하도록 하자.&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;StoreKit&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;인앱 구매나 앱스토어와의 인터렉션 관련 기능을 지원하는 프레임워크&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인앱 리뷰 요청 팝업을 띄우기 위해서는 StoreKit 의 `SKStoreReviewController`를 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;`SKStoreReviewController.reqeustReview(in: )` 을 호출하게 되면 리뷰 팝업이 나타나게 된다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;guard let scene = UIApplication.shared.connectedScenes.first(where: {
  $0.activationState == .foregroundActive
}) as? UIWindowScene else {
  return
}
SKStoreReviewController.requestReview(in: scene)&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://ho8487.tistory.com/119&quot;&gt;[iOS] 사용자에게 어떻게 리뷰를 받을 수 있을까? (+ SKStoreReviewController)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.apple.com/design/human-interface-guidelines/ratings-and-reviews&quot;&gt;Ratings and reviews | Apple Developer Documentation&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS/Swift</category>
      <category>ios</category>
      <category>Swift</category>
      <category>인앱리뷰</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/98</guid>
      <comments>https://iwntberich.tistory.com/98#entry98comment</comments>
      <pubDate>Thu, 24 Oct 2024 15:14:43 +0900</pubDate>
    </item>
    <item>
      <title>[iOS/SwiftUI] Text 더보기 버튼 만들기 - ViewThatFits</title>
      <link>https://iwntberich.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;스유 초보이기 때문에 틀린 내용이 있을 수 있습니다!&lt;/i&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  더보기 Text만들기&lt;/h2&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;방법을 여러 번 검색해보았고, Geometry를 이용하는 방법을 찾아서 해보는데 원하는대로 되지 않았따..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lineLimit이 2일 때 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;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text&quot;&gt;https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713512390035&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;SwiftUI - how know number of lines in Text?&quot; data-og-description=&quot;I have a dynamic text, it can be small or large I what by default show only 3 lines and only if needed add &amp;quot;more&amp;quot; button. When the user tap on this button (&amp;quot;More&amp;quot;) - I will show all test. I ask, h...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text&quot; data-og-url=&quot;https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NOYHl/hyVPNdqBHD/3tZPyzv4Ygme6HXuZq1zp1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316,https://scrap.kakaocdn.net/dn/bqidC9/hyVPUDCj7z/BllKwTUPMSSFbNsoEPAN9k/img.png?width=1012&amp;amp;height=734&amp;amp;face=0_0_1012_734,https://scrap.kakaocdn.net/dn/b1J3J5/hyVPSZ7zZt/88URx9DkH1jnxTgbUo8Knk/img.png?width=498&amp;amp;height=902&amp;amp;face=0_0_498_902&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NOYHl/hyVPNdqBHD/3tZPyzv4Ygme6HXuZq1zp1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316,https://scrap.kakaocdn.net/dn/bqidC9/hyVPUDCj7z/BllKwTUPMSSFbNsoEPAN9k/img.png?width=1012&amp;amp;height=734&amp;amp;face=0_0_1012_734,https://scrap.kakaocdn.net/dn/b1J3J5/hyVPSZ7zZt/88URx9DkH1jnxTgbUo8Knk/img.png?width=498&amp;amp;height=902&amp;amp;face=0_0_498_902');&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;SwiftUI - how know number of lines in Text?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I have a dynamic text, it can be small or large I what by default show only 3 lines and only if needed add &quot;more&quot; button. When the user tap on this button (&quot;More&quot;) - I will show all test. I ask, h...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.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;p data-ke-size=&quot;size16&quot;&gt;다른 답변을 보다보니 ios16.0 버전에서 나온 ViewThatFits를 이용하여 구현할 수 있다길래 시도해봤다.&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;일단은 ViewThatFits에 대해 알아보자!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ViewThatFits&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAassr/btsGL32Kqxm/ZgIjH8jM1pmruojpO1EZlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAassr/btsGL32Kqxm/ZgIjH8jM1pmruojpO1EZlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAassr/btsGL32Kqxm/ZgIjH8jM1pmruojpO1EZlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAassr%2FbtsGL32Kqxm%2FZgIjH8jM1pmruojpO1EZlK%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;2000&quot; height=&quot;768&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ViewThatFits&lt;/code&gt; 는 iOS 16.0이상부터 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewThatFits를 채택한 뷰는 부모 뷰가 크기를 정하는 것이 아니라 자신의 크기에 맞게 뷰의 크기를 조절할 수 있다.&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://developer.apple.com/documentation/swiftui/viewthatfits&quot;&gt;https://developer.apple.com/documentation/swiftui/viewthatfits&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에 코드와 함께 예시로 설명이 되어있는데&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct UploadProgressView: View {
    var uploadProgress: Double

    var body: some View {
        ViewThatFits(in: .horizontal) {
            HStack {
                Text(&quot;\(uploadProgress.formatted(.percent))&quot;)
                ProgressView(value: uploadProgress)
                    .frame(width: 100)
            }
            ProgressView(value: uploadProgress)
                .frame(width: 100)
            Text(&quot;\(uploadProgress.formatted(.percent))&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UploadProgressView의 body가 ViewThatFits로 만들어져있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드는 수평 길이에 따라 텍스트와 ProgressView 모두 보여주거나, PorgressView만 보여주거나 텍스트만 보여주도록 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;VStack {
    UploadProgressView(uploadProgress: 0.75)
        .frame(maxWidth: 200)
    UploadProgressView(uploadProgress: 0.75)
        .frame(maxWidth: 100)
    UploadProgressView(uploadProgress: 0.75)
        .frame(maxWidth: 50)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHPwKH/btsGK6My1AF/tLyTuCjpH7YpvcFDgA0Qzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHPwKH/btsGK6My1AF/tLyTuCjpH7YpvcFDgA0Qzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHPwKH/btsGK6My1AF/tLyTuCjpH7YpvcFDgA0Qzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHPwKH%2FbtsGK6My1AF%2FtLyTuCjpH7YpvcFDgA0Qzk%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;291&quot; height=&quot;149&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 내부에 들어갈 데이터가 모두 들어갈 충분한 공간을 가지고 있다면 첫번째 HStack 내부의 뷰를 보여주고, 그 다음 크기에 맞도록 알맞게 뷰가 그려진다.&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;ViewThatFits는 &lt;b&gt;뷰 내부에 들어갈 컨텐츠의 크기를 계산&lt;/b&gt;하고, &lt;b&gt;해당 컨텐츠가 모두 수용 가능한 크기의 뷰를 골라 그려주는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  더보기 텍스트를 만들어보자&lt;/h2&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct TruncatedTextView: View {
    private var text: String = &quot;&quot;
    private var lineLimit: Int = 2

    @State private var isExpended: Bool = false

    var body: some View {
        Text(text)
            .font(.caption)
            .lineLimit(expended ? nil : lineLimit)
            .overlay(alignment: .bottomTrailing) {
                if !expended {
                    Text(&quot; ..더보기&quot;)
                        .font(.caption)
                        .foregroundStyle(.blue)
                        .underline()
                        .onTapGesture {
                            self.expended.toggle()
                        }
                        .background {
                            RoundedRectangle(cornerRadius: 8)
                                .fill(
                                    LinearGradient(gradient: Gradient(colors: [Color(white: 1, opacity: 0.8), .white]), startPoint: .leading, endPoint: .trailing)
                                )
                        }
                }


          }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lineLimit으로 2줄 제한을 주었다. 2줄 넘어가면 더보기 버튼을 생성하여 확장 가능하도록 구현예정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더보기 버튼은 overlay를 이용하여 텍스트를 올렸고, onTapGesture를 추가하여 expended 토글이 되도록 구현하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  문제점!&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zqFoq/btsGL3PfvGs/eQbFlS1yevcMbdZkSaBHk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zqFoq/btsGL3PfvGs/eQbFlS1yevcMbdZkSaBHk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zqFoq/btsGL3PfvGs/eQbFlS1yevcMbdZkSaBHk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzqFoq%2FbtsGL3PfvGs%2FeQbFlS1yevcMbdZkSaBHk0%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;398&quot; height=&quot;142&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2줄이하의 텍스트가 들어올 경우 더보기버튼이 사라져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트가 2줄 내로 모두 입력이 가능한 경우면 더보기 버튼을 사용할 필요가 없고, 텍스트가 잘리게 된다면 더보기 버튼을 추가하여 확장이 가능하도록 구현하여야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 해결!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewThatFits를 활용하여 텍스트가 2줄이 넘어가는지 판단하도록 구현하였다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;private func calculateTruncation() -&amp;gt; some View {
    ViewThatFits(in: .vertical) {
        Text(text)
            .font(.caption)
                .hidden()
            .onAppear {
                guard isTruncated == nil else { return }
                isTruncated = false
            }
        Color.clear
                .hidden()
            .onAppear {
                guard isTruncated == nil else { return }
                isTruncated = true
            }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수직 길이에 따라 조절할 것이기 때문에 ViewThatFits의 Axis를 vertical로 설정한다.&lt;br /&gt;길이에 맞게 다 들어간다면 isTruncated 값을 false, 잘려지게 된다면 true로 값을 변경한다.&lt;br /&gt;이 메서드가 리턴하는 뷰는 실제로 보여주기 위한 뷰가 아님!&lt;br /&gt;텍스트가 잘리는지 여부를 판단하기 위한 것이기 때문에 &lt;code&gt;.hidden()&lt;/code&gt; 설정을 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct TruncatedTextView: View {
    var body: some View {
        Text(text)
            .font(.caption)
            .lineLimit(isExpended ? nil : lineLimit)
            .background(calculateTruncation()) // 체크 지점!
            .overlay(alignment: .bottomTrailing) {
                if isTruncated == true &amp;amp;&amp;amp; !isExpended {
                    Text(&quot; ..더보기&quot;)
                        .font(.caption)
                        .foregroundStyle(.blue)
                        .underline()
                        .onTapGesture {
                            self.isExpended.toggle()
                        }
                        .background {
                            RoundedRectangle(cornerRadius: 8)
                                .fill(
                                    LinearGradient(gradient: Gradient(colors: [Color(white: 1, opacity: 0.8), .white]), startPoint: .leading, endPoint: .trailing)
                                )
                        }
                }


            }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 뷰를 .background()에서 호출하게 되면 컨텐츠를 텍스트에 삽입할 때 텍스트가 잘리는지 여부를 판단할 수 있고, isTruncated 값을 통해 더보기 버튼을 붙여줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cY9So2/btsGLC5quEa/F49aCLbEVt4tlFLfyGteL1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cY9So2/btsGLC5quEa/F49aCLbEVt4tlFLfyGteL1/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;294&quot; data-filename=&quot;Monosnap screencast 2024-04-17 16-36-55.gif&quot; width=&quot;298&quot; height=&quot;166&quot; style=&quot;width: 41.7296%; margin-right: 10px;&quot; data-widthpercent=&quot;42.22&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cY9So2/btsGLC5quEa/F49aCLbEVt4tlFLfyGteL1/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcY9So2%2FbtsGLC5quEa%2FF49aCLbEVt4tlFLfyGteL1%2Fimg.gif&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;528&quot; height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFYeqa/btsGKUr9iGf/Qw8PM3UBp1QaLHI0v8jepK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFYeqa/btsGKUr9iGf/Qw8PM3UBp1QaLHI0v8jepK/img.png&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;769&quot; data-is-animation=&quot;false&quot; width=&quot;297&quot; height=&quot;121&quot; data-widthpercent=&quot;57.78&quot; style=&quot;width: 57.1076%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFYeqa/btsGKUr9iGf/Qw8PM3UBp1QaLHI0v8jepK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFYeqa%2FbtsGKUr9iGf%2FQw8PM3UBp1QaLHI0v8jepK%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;1890&quot; height=&quot;769&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  전체코드&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;import SwiftUI

struct TruncatedTextView: View {
    private var text: String = &quot;&quot;
    private var lineLimit: Int = 1

    @State private var isTruncated: Bool? = nil
    @State private var isExpended: Bool = false

    init(text: String, lineLimit: Int) {
        self.text = text
        self.lineLimit = lineLimit
    }

    private func calculateTruncation() -&amp;gt; some View {
        ViewThatFits(in: .vertical) {
            Text(text)
                .font(.caption)
                .hidden()
                .onAppear {
                    guard isTruncated == nil else { return }
                    isTruncated = false
                }

            Color.clear
                .hidden()
                .onAppear {
                    guard isTruncated == nil else { return }
                    isTruncated = true
                }
        }
    }

    var body: some View {
        Text(text)
            .font(.caption)
            .lineLimit(isExpended ? nil : lineLimit)
            .background(calculateTruncation())
            .overlay(alignment: .bottomTrailing) {
                if isTruncated == true &amp;amp;&amp;amp; !isExpended {
                    Text(&quot; ..더보기&quot;)
                        .font(.caption)
                        .foregroundStyle(.blue)
                        .underline()
                        .onTapGesture {
                            self.isExpended.toggle()
                        }
                        .background {
                            RoundedRectangle(cornerRadius: 8)
                                .fill(
                                    LinearGradient(gradient: Gradient(colors: [Color(white: 1, opacity: 0.8), .white]), startPoint: .leading, endPoint: .trailing)
                                )
                        }
                }


            }
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS/UIKit&amp;amp;SwiftUI</category>
      <author>김졀니</author>
      <guid isPermaLink="true">https://iwntberich.tistory.com/97</guid>
      <comments>https://iwntberich.tistory.com/97#entry97comment</comments>
      <pubDate>Fri, 19 Apr 2024 16:42:42 +0900</pubDate>
    </item>
  </channel>
</rss>