<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>끄적끄적</title>
        <link>https://velog.io/</link>
        <description>😁</description>
        <lastBuildDate>Wed, 28 Jul 2021 13:43:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>끄적끄적</title>
            <url>https://images.velog.io/images/unknown_horse/profile/c78359a7-e051-46c0-955a-08802fae8f0a/KakaoTalk_Photo_2021-07-17-12-59-57 003.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 끄적끄적. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/unknown_horse" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Protect mutable state with Swift actors]]></title>
            <link>https://velog.io/@unknown_horse/Protect-mutable-state-with-Swift-actors</link>
            <guid>https://velog.io/@unknown_horse/Protect-mutable-state-with-Swift-actors</guid>
            <pubDate>Wed, 28 Jul 2021 13:43:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>WWDC2021 - Protect mutable state with Swift actors 시청 후 정리한 글입니다.</p>
</blockquote>
<h2 id="summary">Summary</h2>
<p>서로 다른 스레드가 가변 상태에 동시 접근할 경우 경쟁 조건이 발생할 수 있습니다.
<strong>Swift Actor를 통해 가변 상태를 보호하는 방법</strong>에 대해 알아봅니다.</p>
<h3 id="data-races-make-concurrency-hard">Data races make concurrency hard</h3>
<p>서로 다른 스레드가 가변 상태에 동시 접근할 때, 그 중 하나 이상의 스레드가 쓰기 작업인 경우 <a href="https://ko.wikipedia.org/wiki/%EA%B2%BD%EC%9F%81_%EC%83%81%ED%83%9C">경쟁 조건</a>이 발생합니다.</p>
<p>데이터 경쟁이 발생하는 코드는 작성하기 쉽지만 디버깅하기 어렵기로 악명이 높습니다.</p>
<pre><code class="language-swift">class Counter {
    var value = 0

    func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached {
    print(counter.increment())
}

asyncDetached {
    print(counter.increment())
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">class Counter {
    var value = 0

    func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    print(counter.increment())
}

asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    print(counter.increment())
}</code></pre>
<p>위 예제의 경우 실행 시점에 따라 결과값( <code>1, 2</code> or <code>2, 1</code> or <code>1, 1</code> or <code>2, 2</code>)이 달라지며, 의도하지 않은 잘못된 값이 발생할 수 있습니다.</p>
<p>데이터 경쟁은 <strong>가변 공유 상태(shared mutable state)</strong>로 인해 발생합니다.</p>
<p>데이터가 불변 상태이거나 여러 작업에서 동시에 공유되지 않으면 데이터 경쟁 문제가 발생하지 않습니다.</p>
<h3 id="value-semantics-help-eliminate-data-races">Value semantics help eliminate data races</h3>
<p>데이터 경쟁을 피하는 방법 중 하나는 값 타입을 사용해서 가변 공유 상태를 제거하는 것입니다.</p>
<pre><code class="language-swift">var array1 = [1, 2]
var array2 = array1

array1.append(3)
array2.append(4)

print(array1) // [1, 2, 3]
print(array2) // [1, 2, 4]</code></pre>
<p>데이터 경쟁을 피하기 위해 <code>counter</code>를 값 타입으로 변경하지만, 상수이기 때문에 컴파일 오류가 발생합니다. (<code>⚠️ Cannot use mutating member on immutable value: &#39;counter&#39; is &#39;let&#39; constant</code>)</p>
<pre><code class="language-swift">struct Counter {
    var value = 0

mutating func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached {
    print(counter.increment()) // Error: Cannot use mutating member on immutable value: &#39;counter&#39; is &#39;let&#39; constant
}

asyncDetached {
    print(counter.increment()) // Error: Cannot use mutating member on immutable value: &#39;counter&#39; is &#39;let&#39; constant
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">struct Counter {
    var value = 0

mutating func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached {
    print(counter.increment()) // Error: Cannot use mutating member on immutable value: &#39;counter&#39; is a &#39;let&#39; constant Change &#39;let&#39; to &#39;var&#39; to make it mutable
}

asyncDetached {
    print(counter.increment()) // Error: Cannot use mutating member on immutable value: &#39;counter&#39; is a &#39;let&#39; constant Change &#39;let&#39; to &#39;var&#39; to make it mutable
}</code></pre>
<p>컴파일 오류를 해결하기 위해<code>counter</code>를 변수로 만들면 될 것 같지만, 이는 다시 데이터 경쟁 문제가 발생합니다.</p>
<p>다행히 컴파일러는 아래와 같은 안전하지 않는 코드를 허용하지 않습니다. (<code>⚠️ Mutation of captured var &#39;counter&#39; in concurrently-executing code</code>)</p>
<pre><code class="language-swift">struct Counter {
    var value = 0

mutating func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

var counter = Counter()
asyncDetached {
    print(counter.increment()) // Error: Mutation of captured var &#39;counter&#39; in concurrently-executing code
}

asyncDetached {
    print(counter.increment()) // Error: Mutation of captured var &#39;counter&#39; in concurrently-executing code
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.
실습간 컴파일 오류는 발생하지 않았습니다.</p>
<pre><code class="language-swift">struct Counter {
    var value = 0

mutating func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

var counter = Counter()
asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    print(counter.increment())
}

asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    print(counter.increment())
}</code></pre>
<p>지역 변수를 통해 컴파일 오류를 해결할 수 있지만, 해당 코드는 잘못된 결과 값을 반환합니다.</p>
<pre><code class="language-swift">struct Counter {
    var value = 0

mutating func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached {
    var counter = counter
    print(counter.increment())
}

asyncDetached {
    var counter = counter
    print(counter.increment())
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">struct Counter {
    var value = 0

mutating func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    var counter = counter
    print(counter.increment())
}

asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    var counter = counter
    print(counter.increment())
}</code></pre>
<p>위 문제는 여전히 가변 공유 상태가 필요한 경우가 있다는 것을 우리에게 알려줍니다.</p>
<h3 id="shared-mutable-state-in-concurrent-programs">Shared mutable state in concurrent programs</h3>
<p>우리는 데이터 경쟁을 피하기 위해 특정 형태의 동기화가 필요합니다.</p>
<p>아래와 같은 방법들을 통해 해결할 수 있지만, 사용자의 주의가 필요합니다.</p>
<ul>
<li>Atomics</li>
<li>Locks</li>
<li>Serial dispatch queues</li>
</ul>
<p>Swift는 보다 쉽게 데이터 경쟁을 해결하기 위해 액터를 도입합니다.</p>
<hr>
<h2 id="actors">Actors</h2>
<p>액터는 가변 공유 상태를 위한 동기화 메커니즘입니다.</p>
<p>액터의 경우 Swift가 기본적으로 Lock, Serial dispatch queues와 같은 상호 배제 속성을 보장합니다.</p>
<h3 id="actor-types">Actor types</h3>
<p>액터는 아래와 같은 특징을 가지고 있습니다.</p>
<ul>
<li>액터는 자체적으로 상태를 가지고 있으며 해당 상태는 프로그램의 나머지 부분과 분리되어 있습니다.</li>
<li>해당 상태에 접근하는 유일한 방법은 액터를 통과하는 것(by going through the actor)입니다.</li>
<li>액터를 통과할 때마다 액터의 동기화 메커니즘은 다른 코드가 상태에 접근하지 못하도록 합니다.</li>
<li>Struct, Enum, Class 와 비슷한 특성을 가집니다.</li>
<li>참조 타입입니다.</li>
</ul>
<pre><code class="language-swift">actor Counter {
    var value = 0

    func increment() -&gt; Int {
        value = value + 1
        return value
    }
}</code></pre>
<h3 id="actor-isolation-prevents-unsynchronized-access">Actor isolation prevents unsynchronized access</h3>
<p>액터의 내부 동기화가 가변 공유 상태에 대한 데이터 경쟁을 없앴기 때문에  <code>1, 2</code> 또는 <code>2, 1</code>과 같은 유효한 결과만 얻을 수 있습니다.</p>
<pre><code class="language-swift">actor Counter {
    var value = 0

    func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached {
    print(counter.increment())
}

asyncDetached {
    print(counter.increment())
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">actor Counter {
    var value = 0

    func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached { // Warining: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    print(counter.increment()) // Error: Expression is &#39;async&#39; but is not marked with &#39;await&#39;
}

asyncDetached { // Warining: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    print(counter.increment()) // Error: Expression is &#39;async&#39; but is not marked with &#39;await&#39;
}</code></pre>
<h3 id="asynchronous-interaction-with-actors">Asynchronous interaction with actors</h3>
<p>액터는 외부와 비동기적으로 상호 작용합니다.</p>
<p>액터가 사용중이면 실행중인 CPU가 다른 작업을 수행할 수 있도록 코드를 중단합니다.</p>
<p>다시 자유를 얻게되면 중단된 코드를 재개합니다.</p>
<p><code>await</code> 키워드를 통해 액터에 대한 비동기 호출에 일시 중단이 포함될 수 있음을 나타냅니다.</p>
<pre><code class="language-swift">actor Counter {
    var value = 0

    func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached {
    print(await counter.increment())
}

asyncDetached {
    print(await counter.increment())
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">actor Counter {
    var value = 0

    func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

let counter = Counter()
asyncDetached { // Warining: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    await print(counter.increment())
}

asyncDetached { // Warining: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    await print(counter.increment())
}</code></pre>
<p><span style="color:red">⚠️ <strong>해당 예제의 경우 실습 환경에서 동작하지 않습니다. (Error: The LLDB RPC server has crashed)</strong></span></p>
<h3 id="synchronous-interaction-within-an-actor">Synchronous interaction within an actor</h3>
<p><code>Counter</code>의 익스텐션에 <code>resetSlowly(to:)</code> 함수를 정의했습니다.</p>
<p>함수는 액터 내부에 있기 때문에 상태에 직접 접근할 수 있으며, <code>increment()</code>와 같은 다른 메서드를 동기적으로 호출할 수 있습니다.</p>
<p>액터에서 실행 중이라는 것을 알고 있기 때문에 기다릴 필요가 없습니다.</p>
<pre><code class="language-swift">extension Counter {
    func resetSlowly(to newValue: Int) {
        value = 0
        for _ in 0..&lt;newValue {
            increment()
        }
        assert(value == newValue)
    }
}</code></pre>
<hr>
<h2 id="actor-reentrancy">Actor reentrancy</h2>
<p>액터의 동기 코드는 중단 없이 완료될 때까지 실행됩니다.</p>
<p>시스템의 다른 비동기 코드와 상호 작용할 경우에 대해 알아봅시다.</p>
<blockquote>
<p><strong>💡<a href="https://ko.wikipedia.org/wiki/%EC%9E%AC%EC%A7%84%EC%9E%85%EC%84%B1">재진입성(Reetrancy)?</a></strong>
컴퓨터 프로그램 또는 서브루틴에 재진입성이 있으면, 이 서브루틴은 동시에(병렬) 안전하게 실행 가능합니다. 즉 재진입이 가능한 루틴은 동시에 접근해도 언제나 같은 실행 결과를 보장합니다.
여러 &#39;사용자/객체/프로세스&#39;와 멀티프로세싱이 대개 재진입 코드의 제어를 복잡하게 만듭니다. 또한 입출력 코드는 디스크나 터미널과 같은 공유 자원에 의존하고 있기 때문에 보통은 재진입성이 없습니다.
재진입성은 함수형 프로그래밍의 핵심 개념입니다.</p>
</blockquote>
<p>이미지 다운로더 액터가 있습니다.</p>
<p>이미지 다운로더는 다른 서비스에서 이미지를 다운받는 역할을 수행하며, 다운 받은 이미지를 캐시에 저장하여 동일한 이미지에 대한 중복 다운로드를 방지합니다.</p>
<pre><code class="language-swift">actor ImageDownloader {
    private var cache: [URL: Image] = [:]

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

        let image = try await downloadImage(from: url)
        cache[url] = image
        return image    
    }
}</code></pre>
<p>액터의 동기화 메커니즘은 하나의 작업만 캐시에 접근할 수 있도록 보장합니다.</p>
<p>이 때 동일한 이미지를 가져오려고 하는 두 개의 동시 작업(<code>Task1</code>, <code>Task2</code>)이 있다고 가정해봅시다.</p>
<p><code>Task1</code>은 캐시가 없음을 확인하고 서버에 이미지 다운로드 요청합니다. </p>
<p><code>Task1</code>작업이 이미지를 다운로드하는 동안 동일한 URL에 새로운 이미지가 배포되었습니다.</p>
<p><img src="https://images.velog.io/images/unknown_horse/post/e292e37a-5935-4b34-a100-cb34e0b452cf/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-07-28%20%EC%98%A4%ED%9B%84%2010.30.54.png" alt=""></p>
<p>새로 배포된 이미지를 <code>Task2</code> 에서 가져올 경우 <code>Task1</code> 에서 가져온 이미지를 덮어쓰는 오류가 발생합니다.</p>
<p><img src="https://images.velog.io/images/unknown_horse/post/ea5eb056-7129-4297-b1c7-e40af653d6fe/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-07-28%20%EC%98%A4%ED%9B%84%2010.31.17.png" alt=""></p>
<p>오류를 해결하기 위해 실행을 재개할 때 캐시에 이미 항목이 있는 경우 원래 버전을 유지하도록 예외처리합니다.</p>
<p><img src="https://images.velog.io/images/unknown_horse/post/b90dabdd-4c3c-43f8-ac0b-2a9636f41e40/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-07-28%20%EC%98%A4%ED%9B%84%2010.31.32.png" alt=""></p>
<p>더 나은 해결 방법은 중복 다운로드를 완전히 피하는 것입니다.</p>
<p>액터 재진입(Actor Reentrancy)은 교착 상태(deadlock)를 방지하고 진행을 보장하지만, <code>await</code>에서 가정(assumption)을 확인해야 합니다.</p>
<p>전역 상태, 시계, 타이머, 액터에 대한 가정은 <code>await</code> 이후 확인이 필요합니다.</p>
<p>재진입을 잘 설계하려면 액터 상태 변경은 동기 코드에서 수행하도록 합니다.</p>
<blockquote>
<p>💡<strong><a href="https://ko.wikipedia.org/wiki/%EA%B5%90%EC%B0%A9_%EC%83%81%ED%83%9C">Deadlock?</a></strong>
두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태를 말한다.</p>
</blockquote>
<hr>
<h2 id="actor-isolation">Actor isolation</h2>
<p>이번 섹션은 프로토콜 준수, 클로저 및 클래스를 비롯한 언어 내 다른 기능과 상호 작용하는 방식에 대해 설명합니다.</p>
<h3 id="protocol-conformace">Protocol conformace</h3>
<p>다른 타입과 마찬가지로 액터는 프로토콜을 따를 수 있습니다.</p>
<p><code>LibraryAccount</code> 액터가 <code>Equtable</code> 프로토콜을 따르는 예제입니다.</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
}

extension LibraryAccount: Equatable {
    static func ==(lhs: LibraryAccount, rhs: LibraryAccount) -&gt; Bool {
        lhs.idNumber == rhs.idNumber
    }
}</code></pre>
<p>정적 메서드로 자체 인스턴스가 없으므로 액터와 격리되지 않습니다.</p>
<p>파라미터로 넘어온 액터 타입의 경우 불변 상태에만 접근하기 때문에 아무런 문제없는 코드입니다.</p>
<h3 id="conformances-must-respect-actor-isolation">Conformances must respect actor isolation</h3>
<p><code>LibraryAccount</code> 액터가 <code>Hashable</code> 프로토콜을 따르는 예제입니다.</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
}

extension LibraryAccount: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(idNumber) // Error: actor-isolated method &#39;hash(into:)&#39; cannot satisfy synchronous requirement
    }
} </code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int = 0
    var booksOnLoan: [Book] = []
}

extension LibraryAccount: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(idNumber) // Error: Actor-isolated instance method &#39;hash(into:)&#39; cannot be used to satisfy a protocol requirement
    }
}</code></pre>
<p><code>hash(into:)</code> 함수는 외부에서 호출될 수 있는데, 비동기 호출이 아니므로 액터 격리를 유지할 수 없다고 오류가 발생합니다. (<code>⚠️ actor-isolated method &#39;hash(into:)&#39; cannot satisfy synchronous requirement</code>)</p>
<p>이 문제를 해결하기 위에 메서드를 비격리 상태로 만들 수 있습니다.</p>
<h3 id="protocol-conformances-and-non-isolated-declearations">Protocol conformances and non-isolated declearations</h3>
<p>해당 메서드가 액터 외부에 있는 것으로 취급하기 위해 <code>nonisolated</code>를 사용합니다.</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
}

extension LibraryAccount: Hashable {
    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(idNumber)
    }
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int = 0
    var booksOnLoan: [Book] = []
    nonisolated var hashValue: Int { // `nonisolated` 미작성시 Error: Actor-isolated property &#39;hashValue&#39; cannot be used to satisfy a protocol requirement
        idNumber
    }
}

extension LibraryAccount: Hashable {
    static func ==(lhs: LibraryAccount, rhs: LibraryAccount) -&gt; Bool {
        lhs.idNumber == rhs.idNumber
    }

    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(idNumber)
    }
}</code></pre>
<p>예제 코드의 경우 불변 상태인 <code>idNumber</code>에 접근하기 때문에 문제없이 동작합니다.</p>
<h3 id="non-isolated-declarations-are-outside-the-actor">Non-isolated declarations are outside the actor</h3>
<p><code>nonisolated</code>의 경우 액터 외부에 있는 것과 동일하게 취급되기 때문에 가변 상태를 참조할 수 없습니다.</p>
<p>만약 <code>booksOnLoan</code>에 접근할 경우 컴파일 오류가 발생합니다. (<code>⚠️ actor-isolated &#39;booksOnLoan&#39; cannot be referenced from outside the actor</code>)</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
}

extension LibraryAccount: Hashable {
    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(booksOnLoan) // Error: actor-isolated &#39;booksOnLoan&#39; cannot be referenced from outside the actor
    }
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int = 0
    var booksOnLoan: [Book] = []
    nonisolated var hashValue: Int {
        idNumber
    }
}

extension LibraryAccount: Hashable {
    static func ==(lhs: LibraryAccount, rhs: LibraryAccount) -&gt; Bool {
        lhs.idNumber == rhs.idNumber
    }

    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(booksOnLoan) // Error: Actor-isolated property &#39;booksOnLoan&#39; can not be referenced from a non-isolated context
    }
}</code></pre>
<h3 id="closures-can-be-isolated-to-the-actor">Closures can be isolated to the actor</h3>
<p>클로저는 함수와 마찬가지로 액터와 격리되거나 격리되지 않을 수 있습니다.</p>
<p>아래는 대출한 책의 일부를 읽고 읽은 총 페이지 수를 반환하는 예제입니다.</p>
<pre><code class="language-swift">extension LibraryAccount {
    func readSome(_ book: Book) -&gt; Int {
        // ...
    }

    func read() -&gt; Int {
        booksOnLoan.reduce(0) { book in
            readSome(book)
        }
    }
}</code></pre>
<p>위 예제는 reduce 작업이 동기적으로 실행되고, 동시 접근을 유발할 수 있는 다른 스레드로 클로저가 벗어날 수 없기 때문에 안전한 코드입니다.</p>
<p><code>asyncDetached</code>는 액터가 수행하는 다른 작업과 동시에 클로저를 실행합니다.</p>
<p>클로저는 액터 외부에 있으므로 <code>await</code>를 사용합니다.</p>
<pre><code class="language-swift">extension LibraryAccount {
    func read() -&gt; Int {
        // ...
    }

    func readLater() {
        asyncDetached {
            await read()
        }
    }
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">extension LibraryAccount {
    func read() -&gt; Int {
        return 0
    }

    func readLater() {
        asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
            await read()
        }
    }
}</code></pre>
<p><span style="color:red">⚠️ <strong>해당 예제의 경우 실습 환경에서 동작하지 않습니다. (Error: The LLDB RPC server has crashed)</strong></span>!</p>
<h3 id="passing-data-into-and-out-of-actors">Passing data into and out of actors</h3>
<p>액터 내 값 타입의 인스턴스에 대해 액터 외부에서 데이터를 패싱하는 것은 괜찮습니다.</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
    func selectRandomBook() -&gt; Book? { }
}

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

func visit(_ account: LibraryAccount) async {
    guard var book = await account.selectRandomBook() else { return }
    book.title = &quot;\(book.title)!!!&quot;
}
</code></pre>
<p><img src="https://images.velog.io/images/unknown_horse/post/60b444c1-27aa-4e6b-a3fe-fc1e73844545/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-07-28%20%EC%98%A4%ED%9B%84%2010.33.33.png" alt=""></p>
<p>이번엔 값 타입을 클래스로 변경해보겠습니다.</p>
<p> <code>visit(:)</code> 함수 내부에 책에 대한 참조가 생성되어, 액터 외부에서 가변 공유 상태에 대해 변경할 수 있게 되었습니다. (⚠️ 데이터 경쟁 발생)</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
    func selectRandomBook() -&gt; Book? { }
}

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

func visit(_ account: LibraryAccount) async {
    guard var book = await account.selectRandomBook() else { return }
    book.title = &quot;\(book.title)!!!&quot;
}
</code></pre>
<p><img src="https://images.velog.io/images/unknown_horse/post/d4603596-c839-4b2d-a431-cecf1b958171/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-07-28%20%EC%98%A4%ED%9B%84%2010.34.40.png" alt=""></p>
<p>값 타입과 액터를 함께 사용하는 것은 안전하지만 클래스는 문제를 야기할 수 있습니다.</p>
<p>이를 해결하기 위해 <code>Sendable</code>타입을 사용합니다.</p>
<h3 id="sendable-types">Sendable types</h3>
<p><code>Sendable</code>은 다른 액터 간에 값을 공유할 수 있는 타입입니다.</p>
<ul>
<li><p>값 타입의 경우 복사본이 독립적이기 때문에 <code>Sendable</code>입니다.</p>
</li>
<li><p>액터는 가변 상태 접근에 대해 동기화하기 때문에 <code>Sendable</code>입니다.</p>
</li>
<li><p>자신과 모든 하위 클래스가 변경할 수 없는 데이터만 가진 클래스의 경우 <code>Sendable</code>이 될 수 있습니다. (Immutable Classes)</p>
</li>
<li><p>내부적으로 동기화(예: Lock)를 수행하여 안전한 접근이 보장되는 클래스의 경우 <code>Sendable</code>이 될 수 있습니다. (Internally-synchronized class)</p>
</li>
</ul>
<h3 id="checking-sendable">Checking Sendable</h3>
<p><code>Sendable</code>타입은 데이터 경쟁으로부터 코드를 보호합니다. </p>
<p>non-Sendable 타입의 경우 Swift가 정적으로 검사하여 오류를 발생합니다. (<code>⚠️ call to actor-isolated method &#39;selectRandomBook&#39; returns non-Sendable type &#39;Book?&#39;</code>)</p>
<pre><code class="language-swift">actor LibraryAccount {
    let idNumber: Int
    var booksOnLoan: [Book] = []
    func selectRandomBook() -&gt; Book? { }
}

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

func visit(_ account: LibraryAccount) async {
    guard var book = await account.selectRandomBook() else { return }    // Error: call to actor-isolated method &#39;selectRandomBook&#39; returns non-Sendable type &#39;Book?&#39;
    book.title = &quot;\(book.title)!!!&quot;
}
</code></pre>
<h3 id="adoptinng-sendable">Adoptinng Sendable</h3>
<p><code>Book</code> 구조체의 모든 속성이 <code>Sendable</code>인 경우 <code>Book</code> 구조체는 <code>Sendable</code>이 될 수 있습니다.</p>
<p><code>Sendable</code>이 아닌 속성을 가진 경우 컴파일 오류가 발생합니다. (<code>⚠️ Sendable type &#39;Book&#39; has non-Sendable stored property &#39;authors&#39; of type &#39;[Author]&#39;</code>)</p>
<pre><code class="language-swift">struct Book: Sendable {
    var title: String
    var authors: [Author] // Error: Sendable type &#39;Book&#39; has non-Sendable stored property &#39;authors&#39; of type &#39;[Author]&#39;
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">struct Book: Sendable {
    var title: String
    var authors: [Author] // Error: Stored property &#39;authors&#39; of &#39;Sendable&#39;-conforming struct &#39;Book&#39; has non-sendable type &#39;[Author]&#39;
}</code></pre>
<p>제네릭의 경우 인자(arguments)에 따라 달라집니다.</p>
<pre><code class="language-swift">struct Pair&lt;T, U&gt; {
    var first: T
    var second: U
}

extension Pair: Sendable where T: Sendable, U: Sendable {
}</code></pre>
<p>위의 경우 두 인자가 모두 <code>Sendable</code>인 경우에만 <code>Sendable</code>이 됩니다.</p>
<h3 id="sendable-functions">@Sendable functions</h3>
<p><code>@Sendable 함수 타입</code>은 <code>Sendable</code>프로토콜을 준수합니다.</p>
<p><code>@Sendable</code>은 클로저에 대해 아래와 같은 제약을 두었습니다.</p>
<ul>
<li>가변 상태를 캡처할 수 없습니다. (No mutable captures)</li>
<li>캡처 대상은 <code>Sendable</code>타입입니다. (Captures must be of Sendable type)</li>
<li>동기식 Sendable 클로저는 외부에서 코드를 실행할 수 있기 때문에 액터를 격리할 수 없습니다. (Cannot be both synchronous and actor-isolated)</li>
</ul>
<pre><code class="language-swift">func asyncDetached&lt;T&gt;(_ operation: @Sendable () async -&gt; T) -&gt; Task.Handle&lt;T. Never&gt;</code></pre>
<h3 id="sendable-closure-restriction">@Sendable closure restriction</h3>
<p>Sendable 클로저는 가변 지역 변수에 대한 데이터 경쟁이 발생할 수 있기 때문에 캡처할 수 없습니다. (<code>⚠️ Mutation of captured var &#39;counter&#39; in concurrently-executing code</code>)</p>
<pre><code class="language-swift">struct Counter {
    var value = 0

    mutating func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

var counter = Counter()
asyncDetached {
    print(counter.increment()) // Error: Mutation of captured var &#39;counter&#39; in concurrently-executing code
}

asyncDetached {
    print(counter.increment()) // Error: Mutation of captured var &#39;counter&#39; in concurrently-executing code
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">struct Counter {
    var value = 0

    mutating func increment() -&gt; Int {
        value = value + 1
        return value
    }
}

var counter = Counter()
asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    print(counter.increment())
}

asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
    print(counter.increment())
}</code></pre>
<p>Sendable 클로저는 액터를 격리할 수 없으므로 비동기여야 합니다. (<code>⚠️ call to asctor-isolated method &#39;read&#39; must be &#39;async&#39;</code>)</p>
<pre><code class="language-swift">extension LibraryAccount {
    func read() -&gt; Int { }

    func readLater() {
        asyncDetached {
            read() // Error: call to asctor-isolated method &#39;read&#39; must be &#39;async&#39;
        }
    }
}</code></pre>
<p>아래는 <strong>Xcode 13.0 beta 3</strong>에서 실습한 코드입니다.</p>
<pre><code class="language-swift">extension LibraryAccount {
    func read() -&gt; Int { }

    func readLater() {
        asyncDetached { // Warning: &#39;asyncDetached(priority:operation:)&#39; is deprecated: `asyncDetached` was replaced by `Task.detached` and will be removed shortly.
            read() // Error: Expression is &#39;async&#39; but is not marked with &#39;await&#39;
        }
    }
}</code></pre>
<hr>
<h2 id="main-actor">Main Actor</h2>
<p>메인 스레드에서 동작을 보장하는 액터입니다.</p>
<h3 id="interacting-with-the-main-thread">Interacting with the main thread</h3>
<p>메인 스레드는 UI 렌더링, 사용자와 상호 작용하는 이벤트 등 중요한 작업들을 처리합니다.</p>
<p>모든 작업을 메인 스레드에서 처리할 경우 UI가 멈추는 등의 문제가 발생합니다.</p>
<p>그렇기 때문에 우리는 입/출력 작업이 느리거나, 네트워크 통신 등 시간이 오래 걸리는 작업을 다른 스레드에서 처리하고, 필요한 경우 <code>DisPatchQueue.main.async</code>를 통해 메인 스레드에서 실행할 작업을 작성합니다.</p>
<pre><code class="language-swift">func checkedOut(_ booksOnLoan:[Book]) {
    booksView.checkedOutBooks = booksOnLoan
}

DispatchQueue.main.async {
    checkedOut(booksOnLoan)
}</code></pre>
<h3 id="the-main-actor">The main Actor</h3>
<p>메인 스레드에서 실행을 보장하는 특별한 메인 액터가 있습니다. </p>
<p>일반 액터와 두 가지 차이점이 있습니다.</p>
<ol>
<li>메인 액터는 디스패치 큐를 통해 모든 동기화를 수행합니다. 즉, 런타임 관점에서 <code>DispatchQueue.main</code>로 대체해서 사용할 수 있습니다.</li>
<li>메인 스레드에 있어야 할 코드와 데이터가 여기저기 흩어져 있습니다. (SwiftUI, AppKit, UIKit, 기타 시스템 프레임워크)</li>
</ol>
<p>Swift concurrency를 사용하면 <code>@MainActor</code>를 표시하여 메인 액터에서 실행되어야 한다고 작업할 수 있습니다.</p>
<pre><code class="language-swift">@MainActor func checkedOut(_ booksOnLoan:[Book]) {
    booksView.checkedOutBooks = booksOnLoan
}

await checkedOut(booksOnLoan)</code></pre>
<p>메인 액터 외부에서 호출하는 경우 대기해야 하므로 메인 스레드에서 비동기적으로 수행됩니다.</p>
<p>메인 스레드에서 실행할 코드들을 <code>@MainActor</code>로 표시하면 <code>DispatchQueue.main</code>을 언제 사용해야 할 지 고민할 필요가 없습니다.</p>
<p>Swift는 해당 코드들을 항상 메인 스레드에서 실행되도록 합니다.</p>
<h3 id="main-actor-types">Main actor types</h3>
<p>타입도 <code>@MainActor</code>로 정의하여 모든 멤버, 하위 클래스들을 메인 액터에 있게 할 수 있습니다.</p>
<p>메인 스레드에서 실행되어야 하는 UI와 상호 작용하는 코드들에서 자주 사용합니다.</p>
<p>메소드는 개별적으로 <code>nonisolated</code>를 통해 opt-out 할 수 있습니다.</p>
<pre><code class="language-swift">@MainActor class MyViewController: UIViewController {
    func onPress(...) { ... } // implicitly @MainActor

    nonisolated func fetchLatestAndDisplay() async { ... }
}</code></pre>
<hr>
<h2 id="actors-1">Actors</h2>
<p><img src="https://images.velog.io/images/unknown_horse/post/17647979-9dd8-4f22-80b6-f27a58b0a111/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-07-28%20%EC%98%A4%ED%9B%84%2010.17.53.png" alt=""></p>
<hr>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://developer.apple.com/videos/play/wwdc2021/10133">https://developer.apple.com/videos/play/wwdc2021/10133</a></li>
<li><a href="https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md">https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md</a></li>
<li><a href="https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md">https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EC%9E%AC%EC%A7%84%EC%9E%85%EC%84%B1">https://ko.wikipedia.org/wiki/재진입성</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EA%B5%90%EC%B0%A9_%EC%83%81%ED%83%9C">https://ko.wikipedia.org/wiki/교착_상태</a></li>
</ul>
<hr>
<p>수정이 필요한 부분 있으면 피드백 부탁드립니다. 😁</p>
]]></description>
        </item>
    </channel>
</rss>