Background Thread를 사용해야 하는 이유

작업을 할 때 무거운 작업을 하는 경우 CPU를 많이 사용하기 됩니다. 다음은 현재 사용중인 CPU 사용률을 확인하는 방법입니다.

아래에서 첫 번째 Thread인 Thread 1은 메인 스레드이며 대부분의 코드들이 기본적으로 메인 스레드인 Thread 1에서 작업이 됩니다.

 

Thread 1 즉 메인 스레드에서 작업을 가볍게 하는 경우는 상관 없지만 만약 무거운 작업을 하게 되어 Thread 1 메인 스레드가 포화 상태가 된다면 다른 작업들은 할 수 없게 되며 이게 굉장히 안좋은게 UI 변화 같은 경우도 메인 스레드에서 작업을 하기 때문에 화면이 버벅인다거나 멈출 수 있기 때문입니다.

 

하지만 CPU는 메인 스레드 외에도 여러개의 스레드를 만들어 낼 수 있습니다. (프로세스는 여러개의 스레드로 이루어져 있습니다)

 

다음은 백그라운드 스레드에서 데이터를 가져와 메인 스레드에서 UI를 바꾸는 코드입니다.

@Observable class BackgroundThreadViewModel {
    
    var dataArray: [String] = []
    
    func fetchData() {
        // 백그라운드 스레드에서 다운로드하기
        DispatchQueue.global().async {
            let newData = self.downloadData()
            // 메인 스레드에서 UI 변경하기
            DispatchQueue.main.async {
                self.dataArray = newData
            }
        }
    }
    
    func downloadData() -> [String] {
        var data: [String] = []
        
        for x in 0..<100 {
            data.append("\(x)")
            print(data)
        }
        return data
    }
}

struct ContentView: View {
    
    @Environment(BackgroundThreadViewModel.self) var vm
    
    var body: some View {
        ScrollView {
            VStack(spacing: 10) {
                Text("LOAD DATA")
                    .font(.largeTitle)
                    .fontWeight(.semibold)
                    .onTapGesture {
                        vm.fetchData()
                    }
                
                ForEach(vm.dataArray, id: \.self) { item in
                    Text(item)
                        .font(.headline)
                        .foregroundStyle(.red)
                }
            }
        }
    }
}

 

DispatchQueue.global

global은 다음과 같이 qos(우선순위) 여러개의 종류를 선택할 수 있습니다.

qos란? DispatchQueue에서 수행 할 작업을 분류하기 위해 사용됩니다. 또한 시스템이 우선순위를 정하고 이에 따라서 스케줄링을 하게 됩니다.

DispatchQueue.global(qos: .userInteractive) {}  //Main Queue처럼 UI 변화, Main thread는 아닙니다
DispatchQueue.global(qos: .userInitiated) {}    //유저가 시작한 작업, 유저가 작이 완료될 때까지 작업 X
DispatchQueue.global(qos: .background) {}       //유저가 알아차리지 못하는 작업
DispatchQueue.global(qos: .default) {}          //userInitiated와 utility의 중간
DispatchQueue.global(qos: .utility) {}          //시간이 걸리며 즉각적인 응답이 필요하지 않은 경우
DispatchQueue.global(qos: .unspecified) {}

 

 

실험해보기

다음은 버튼을 누르게 됐을 때 fetchData를 통해서 데이터를 가져오고 그 데이터를 화면에 띄우는 작업입니다.

@Observable class BackgroundThreadViewModel {
    
    var dataArray: [String] = []
    
    func fetchData() {
        // 백그라운드 스레드에서 다운로드하기
        DispatchQueue.global(qos: .background).async {
            let newData = self.downloadData()
            // 메인 스레드에서 UI 변경하기
            DispatchQueue.main.async {
                self.dataArray = newData
            }
        }
    }
    
    func downloadData() -> [String] {
        var data: [String] = []
        
        for x in 0..<100 {
            data.append("\(x)")
            print(data)
        }
        return data
    }
}

struct ContentView: View {
    
    @Environment(BackgroundThreadViewModel.self) var vm
    
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 10) {
                Text("LOAD DATA")
                    .font(.largeTitle)
                    .fontWeight(.semibold)
                    .onTapGesture {
                        vm.fetchData()
                    }
                
                ForEach(vm.dataArray, id: \.self) { item in
                    Text(item)
                        .font(.headline)
                        .foregroundStyle(.red)
                }
            }
        }
    }
}

 

위에 CPU 사용량을 확인하였을 때 알 수 있는 것은 데이터를 가져올 때는 Thread 12와 Thread 13을 사용하였고 UI의 변화를 적용하는 데에는 메인 스레드인 Thread1의 사용량이 올라갔음을 확인할 수 있었습니다.

 

MainThread에서 작업 하는지 확인하는 방법

Thread.isMainThread 또는 Thread.current 를 사용하여 현재 작업이 메인 스레드에서 진행되는지, 진행되고 있는 스레드가 무슨 스레드인지를 확인할 수 있습니다.

func fetchData() {
    // 백그라운드 스레드에서 다운로드하기
    DispatchQueue.global(qos: .background).async {
        let newData = self.downloadData()

        print("CHECK 1: \(Thread.isMainThread)")
        print("CHECK 1: \(Thread.current)")
        // 메인 스레드에서 UI 변경하기
        DispatchQueue.main.async {
            self.dataArray = newData
            print("CHECK 2: \(Thread.isMainThread)")
            print("CHECK 2: \(Thread.current)")
        }
    }
}

 

'SwiftUI' 카테고리의 다른 글

Weak Self  (0) 2024.04.07
CoreBluetooth로 아두이노 불 켜기  (1) 2024.04.07
버튼 커스텀화하기, Custom ButtonStyle  (0) 2024.04.07
Custom ViewModifiers  (0) 2024.04.07
@FocusState 로 키보드 다루기  (0) 2024.04.06
ytw_developer