티스토리 뷰

반응형

 

Swift Concurrency 관련 Instruments 는 총 두가지가 있다. 

이 중, Swift Tasks 를 활용하는 법을 기록! 

 

 

 

WWDC 23 > Analyze hanges with Instruments   와 비슷한 예제를 만들어서 진행해보자.

 

 

# 1. 

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack {
                ForEach(0..<1000) { i in
                    DummyImageView()
                }
            }
        }
    }
}

struct DummyImageView: View {

    @State private var image: UIImage?
    let background = BackyardBackground()
    
    var body: some View {
        if let image {
            Image(uiImage: image)
        } else {
            ProgressView()
                .task {
                    image = background.thumbnail
                }
        }
    }
}

struct BackyardBackground {
    
    var thumbnail: UIImage? {
        get {
            let dummy = UIImage(named: "buzz")
            return dummy?.preparingThumbnail(of: CGSize(width: 100, height: 100))
        }
    }
}

 

 

Product > Profile > Swift Concurrency 를 열어준다.

(혹은 다른 툴을 열고 Instruments Library 에서 Swift Tasks 를 추가해도 무방하다) 

 

 

 

레코딩을 해준 후, 

App > Thread > Graph Display 에서 Task State 와 CPU Usage 를 두개를 선택해서 

다 볼 수 있게 한다.

 

 

 

 

확대해보면 Main Thread 내에 여러 Task 들이 동기적으로 실행되고 있음을 볼 수 있다. 

 

 

 

Swift Tasks 부분을 열어보면 synchronous 하게 진행됨을 한번 더 확인할 수 있다. 

각 Task 를 누르면 아래 설명에서 priority 도 볼 수 있다.

 

 

 

🤔 task 내에서 Thread.current 콘솔에 찍어보는데, 이 정보는 어디서 볼 수 있을까 ? 

<_NSMainThread: 0x600001708040>{number = 1, name = main}

 

 

Task 를 클릭하고

아래에서 활성화된 Task 가 속한 쓰레드를 확인하면 될 것 같다. 

 

 

 

 

 

# 2.

 

이제 코드를 분석해보자! 

 

 

 

- body 가 SwiftUI의 View 프로토콜로부터 @MainActor 를 상속받는다. 

- task modifier 의 클로저가 주변 context 의 actor isolation 을 상속받는다.

 

 

 

- thumbnail getter 가 synchronous 하기 때문에 메인스레드에서 동기적으로 실행된다. 

 

 

 

task 를 main actor 로 부터 분리시키려면 어떻게 해야할까 ? 

두가지 방법이 있다.

 

 

 

1) asynchronously calling an function that's not bound to the main actor allows the task to go off of the main actor. 

2) explicitly detach the task from the surrounding actor context by using "Task.detached", but it is a heavy handed approach and creating a separate task is more expensive than simply suspending an existing one. 

(ex. SwiftUI 는 관련 뷰가 사라질때 task modifier 로 생성된 task 를 자동으로 취소한다. 그 취소가 Task.detached 와 같은 unstructured task 로 전파되지 않기 때문에 직접 관리 해야함) 

 

 

 

1번 비동기 호출로 가자! 

 

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack {
                ForEach(0..<1000) { i in
                    DummyImageView()
                }
            }
        }
    }
}

struct DummyImageView: View {

    @State private var image: UIImage?
    let background = BackyardBackground()
    
    var body: some View {
        if let image {
            Image(uiImage: image)
        } else {
            ProgressView()
                .task {
                    ✅ image = await background.thumbnail
                }
        }
    }
}

struct BackyardBackground {
    
    var thumbnail: UIImage? {
        ✅ get async {
            let dummy = UIImage(named: "buzz")
            return dummy?.preparingThumbnail(of: CGSize(width: 100, height: 100))
        }
    }
}

 

 

이렇게 코드를 바꾸고 다시 프로파일링 해보자.

아까랑 다르게 비동기로 진행됨을 확인할 수 있다.

 

 

 

 

 

 

 

 

 

# 참고 

더 자세히 보려면 Instruments Library 에서 Thread State Trace 를 추가할 수 도 있다. 

 

 

 

 

 

(내가 작성한 간단한 예제에서는 이런 유용한 정보를 얻을 수 없지만 알고 있음 좋을 듯)

 

 

 

반응형
댓글