[WWDC 21] Protect Mutable state with Swift actors

suojae·2024년 1월 30일
0

Actor 등장배경

위와 같이 value 프로퍼티를 비동기로 접근하면 데이터 경쟁 상황이 발생한다.
상황에 따라 첫번째 프린트 문에서 1 혹은 2가 출력되어 결과값을 예측할 수 없다.
만약 두 개의 비동기를 돌리더라도 연산 결과인 2를 확정짓고 싶다면 어떻게 해야할까?


구조체를 사용하면 데이터 경쟁 문제를 해결할 수 있으나 상태를 공유하지 않는다는 문제점이 있다.


상태를 공유하면서 데이터 경쟁 문제를 해결하려면
1. Atomic 연산 (연산이 시작되면, 그것이 완전히 끝날 때까지 다른 스레드나 프로세스에 의해 중단되거나 방해받지 않음)
2. Lock을 걸거나
3. Serial dispatch queue를 거는 방법이 있다.


Actor 소개

Actor의 상태는 오직 자기 자신에 의해서만 변경된다.
또한 serial한 접근을 자체적으로 보장시켜준다.

Actor의 기본목표는 상태공유에 있기 때문에 참조타입이다.


위 코드의 문제점은 저장된 캐시 이미지를 가져다가 쓰는 것이 아닌 비동기로 진행된 Task2에 의한 새로운 이미지를 리턴한다는 점이다.

actor ImageDownloader {

    private enum CacheEntry {
        case inProgress(Task<Image, Error>)
        case ready(Image)
    }

    private var cache: [URL: CacheEntry] = [:]

    func image(from url: URL) async throws -> Image? {
        if let cached = cache[url] {
            switch cached {
            case .ready(let image):
                return image
            case .inProgress(let task):
                return try await task.value
            }
        }

        let task = Task {
            try await downloadImage(from: url)
        }

        cache[url] = .inProgress(task)
                do {
            let image = try await task.value
            cache[url] = .ready(image)
            return image
        } catch {
            cache[url] = nil
            throw error
        }
    }
}

위와 같이 진행중이라면 기다리게 하고 task2 가 들어오면 딕셔너리 값을 꺼내서 재다운로드하지않고 같은 녀석을 꺼냄으로써 문제를 해결할 수 있다.


Actor isolation

위의 예시에서 hash(into:)는 동기적으로 작동하기 때문에 actor외부에서 안전하게 처리해주는 모양새가 된다. actor는 외부에서는 비동기로 처리하고 내부에서 자체 메커니즘에 의해 serial하게 처리된다.


nonisolated 키워드는 액터의 특정 메소드나 프로퍼티가 액터의 격리된 상태에서 벗어나 동작하도록 만들어 준다. 다시말해 내외부를 모두 동기적으로 작동하게 만들어준다, 즉 비동기적 처리 요소가 사라진다.


@Sendable

@Sendable을 통해 비동기 처리할 객체를 안전하게 보장해줄 수 있다.

???


Main Actor

profile
Hi 👋🏻 I'm an iOS Developer who loves to read🤓

0개의 댓글