꿈돌이랜드

Swift Concurrency를 공부하며 느낀점 본문

Programming/Swift

Swift Concurrency를 공부하며 느낀점

loinsir 2025. 3. 17. 22:14
반응형

Swift Concurrency

  • 사내에서 Swift Concurrency 스터디를 진행했습니다.
  • Swift Concurrency는 Swift5.5 버전 부터 도입된 Swift 언어 차원의 비동기 처리 방식입니다
  • Documentation
  • 이 글에서는 개인적으로 Swift Concurrency를 공부하면서 중요하게 깨달은 내용에 대해 서술합니다.

1. 어떤 actor에 해당 변수, 함수가 “선언”되어있는지가 중요하다.

  • GCD의 경우 선언이 중요한게 아니라 런타임에 어떤 dispatchqueue에 해당 작업이 들어가서 실행되는지가 중요했습니다.
  • Swift Concurrency의 경우는 해당 변수와 함수가 “선언된 위치”가 곧 어떤 스레드(엄밀히 말하면 actor)에서 동작할지를 결정합니다.
  • 이를 흔히 다양한 교육자료에서 격리(isolated)라고 표현이 되는데, 직역으로는 당연히 맞는 말이지만, 처음 공부하는 입장에서는 어렵게 느껴지는 요인이었습니다.
class GCDExample {
    private let queue = DispatchQueue(label: "com.example.gcd", attributes: .concurrent)
    private var counter = 0

    func increment() {
        queue.async {
            self.counter += 1
            print("GCD Counter: \(self.counter)")
        }
    }
}

let gcdExample = GCDExample()
gcdExample.increment()

// Swift Concurrency
actor CounterActor {
    private var counter = 0

    func increment() {
        counter += 1
        print("Actor Counter: \(counter)")
    }
}

let counterActor = CounterActor()
Task {
    await counterActor.increment()
}

2. 어떤 스레드에서 실행되는지 보다는 어떤 “actor”에서 실행되는지를 생각하자

  • Swift Concurrency는 미리 스레드를 대략 CPU 코어 수 만큼 여럿 만들어 놓고(협력적 스레드풀, Cooperative Thread pool) 이 중 쉬고 있는 스레드를 골라서 실행되게 하는 방법입니다.
  • actor는 내부에 unownedExecutor라는 큐를 통해 본인의 변수나 함수에 접근하는 task를 순차적으로 실행되게 하여 data race를 방지합니다.
  • await 키워드는 액터를 전환하면서, 해당 액터로부터의 작업을 기다리는 키워드로 이해하면 되겠습니다.
    • 실행되는 스레드는 시스템이 알아서 지정해줍니다.
  • 단 MainActor는 반드시 메인 스레드에서 작동함을 보장합니다.

3. 암시적인(숨겨진) 주변 문맥을 숙지해야한다.

  • UIKit, SwiftUI 에서 제공하는 다양한 클래스들은 MainActor로 마킹되어 있어, 메인스레드에서 실행되는 것을 보장합니다.
    • UIViewController 공식문서 선언부
    • @MainActor class UIViewController
    • SwiftUI View 프로토콜역시 MainActor로 마킹되어있습니다.
  • 따라서 UI LifeCycle 메서드내에 선언된 Task 들은 MainActor에서 실행되는 Task입니다.
  • Task는 주변 actor 컨텍스트를 상속받습니다.
  • 단, Task.detached로 이를 끊을 수 있습니다.
import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 이 Task는 MainActor를 상속받음 (메인 스레드에서 실행됨)
        Task {
            print("Executing on Main Thread: \\(Thread.isMainThread)") // true
        }

        // Task.detached를 사용하면 MainActor 컨텍스트에서 벗어남 (백그라운드에서 실행될 수 있음)
        Task.detached {
            print("Executing on Background Thread: \\(Thread.isMainThread)") // false (일반적으로)
        }
    }
}

  • 만약 아무런 컨텍스트없는 Task의 경우 명시적으로 @MainActor를 지정하지 않는 한 백그라운드에서 실행될 수 있습니다.
    • 어떤 actor에도 속하지 않는 글로벌 컨텍스트에서 실행됩니다.
반응형