How to use Actors and non-isolated in Swift | Swift 동시성 #9

Sung Daegyu·2024년 2월 28일
post-thumbnail

Swift: How to use Actors and non-isolated in Swift | Swift Concurrency #9

이번 강의에서는 Actor의 사용법에 대해 배웠다.

  1. 처음에 Actor를 사용하지 않을 경우 마주할 수 있는 문제점에 대해 알아보고
  2. Actor를 사용하지 않고 해당 문제를 해결할 수 있는 방법을 알아보고
  3. Actor를 사용해 문제를 간단하게 해결하는 방법을 알아본다.

문제점 | Data Race problem

  • 해당 문제를 만나기 위해서 먼저 여러 thread에서 공통된 class의 data에 접근하는 환경을 만들어야하는데, 이를 위해 Timer의 publisher를 사용한다.

    let timer = Timer.publish(every: 0.1, tolerance: nil, on: .main, in: .common, options: nil).autoconnect()
  • 그리고 서로 다른 view에서 싱글톤 class의 data에 접근하도록 만든다.

    class MyDataManager {
      static let instance = MyDataManager()
      private init() { }
      
      var data: [String] = []
      
      func getRandomdata() -> String? {
        self.data.append(UUID().uuidString)
        print(Thread.current)
        return self.data.randomElement()
      }
    }
    • 이 코드에서 self.data.append(UUID().uuidString) 이 부분이 data race가 발생하는 지점인데, 그 이유는 서로 다른 뷰에서 0.1초마다 같은 class의 [String]에 append 하는데, append 하는 타이밍이 겹칠 수 있기 때문이다.

    • Xcode → Edit Scheme → Thread Sanitizer 를 키면 thread unsafe 한 경우 경고문을 보여주는데 다음을 확인할 수 있다.

      • 같은 0x7b0800004b9b0 데이터를 Thread 5와 Thread 1 에서 변경하고 있다고 에러가 발생한다.

해결방법 | Non-Actor

  • Actor를 사용하지 않고 이 문제를 해결하기 위해서는 DispatchQueue를 사용해야 한다. 다른 사용자들은 이를 lock이라고 부른다고 한다.
    private let lock = DispatchQueue(label: "com.MyApp.MyDataManger")
  • 그리고 class 내 함수를 completionHandlerlock을 사용해서 다음과 같이 변경한다.
    func getRandomdata(completionHandler: @escaping (_ title: String?) -> Void ){
        lock.async {
          self.data.append(UUID().uuidString)
          print(Thread.current)
          completionHandler(self.data.randomElement())
        }
      }
  • 마지막으로 데이터를 접근하는 view에서 다음과 같이 await를 사용해준다.
    .onReceive(timer) { _ in
      Task {
        if let data = await manager.getRandomdata() {
          await MainActor.run {
            self.text = data
          }
        }
      }
    }
  • 원리는 class 내에 dispatchqueue를 하나 지정하고 해당 queue(lock) 을 사용해서, 함수가 실행될때마다 접근하는 여러 thread를 줄 세우는(?) 느낌으로 기다리게 하는 것이다. 그래서 await를 통해 값을 받는 것.

해결방법 | Actor

  • Actor를 사용하면 문제가 매우 간단해진다.
  • Actor 자체가 Thread safe 하게 설계되었기 때문이다.
  • class → actor로 변경하면 끝.
    actor MyActorDataManager {
      static let instance = MyActorDataManager()
      private init() { }
      
      var data: [String] = []
      
      func getRandomdata() -> String? {
        self.data.append(UUID().uuidString)
        print(Thread.current)
        return self.data.randomElement()
      }
      
      // DONT WORRY ABOUT TRREAD SAFTY
      nonisolated func getSavedData() -> String {
        return "NEW DATA"
      }
    }
  • Thread safty를 신경쓸 필요 없는 변수/함수의 경우 nonisolated 키워드를 붙여준다.

참고

SwiftfulThinking 강의
https://www.youtube.com/watch?v=UUdi137FySk&list=PLwvDm4Vfkdphr2Dl4sY4rS9PLzPdyi8PM&index=10

profile
대규의 개발로그

0개의 댓글