UIKit

iOS | GCD에 관하여 - 1

ziziDev 2024. 7. 15. 21:10
반응형

 

Thread, Task, Dispatch Queue, GCD

 

Thread / Task

Thread - 노동자

Task - 일

 

 

스레드

단일 스레드, Multi-Thread, 멀티 스레드, Multithreading, 멀티 스레딩

www.ktword.co.kr

 


하나의 일을 절대 한 곳(main Thread)에서 처리하는건 무리가 있음

iOS에서 작업을 한 곳에서 처리하는것 같이 보여도 사실상 OS가 분산처리를 하고 있기 때문에(dispatch queue)

선입 선출로 빠져나갑니다

 

그래서

GCD란 뭘까?

다수의 스레드에 적절히 분배시키기 위해서 task들을 queue에 넣는것인데 

 

Grand Centeral Dispatch

스레드를 적절히 생성해서 분배해주는 방법이고

GCD에서 사용하는 queue의 이름이 Dispatch Queue입니다

 

Dispatch Queue에 작업을 추가하면 GCD는 작업에 맞는 스레드를 자동으로 생성해서 실행하고

종료하면 스레드가 자동으로 해제됩니다


 

 

DispatchQueue.main 역할

 

UI 업데이트

UIKit는 스레드가 안전하지 않기 때문에 UI와 관련된 모든 작업은 메인 스레드에서 수행되어야 합니다

EX) 뷰를 추가하거나 제거, UI요소의 속성을 변경, Animation 실행 등의 작업은

DispatchQueue.main.async를 사용하여 메인스레드에서 처리해야합니다

 

이벤트 처리

터치 이벤트, 제스처 인식, 알림 등의 사용자 인터페이스 관련 이벤트는 메인스레드에서 처리됩니다

EX) 터치 이벤트가 발생하면 시스템은 메인스레드의 런루프에서 처리하고 이를 기반으로 UI를 업데이트합니다

 

동기화

메인스레드는 앱의 중요한 상태를 관리하기 때문에 백그라운드 작업이 완료된 후 결과를 UI에 반영할 때 메인 스레드의 동기화가

필요합니다

EX) 네트워크 요청이 완료된 후 결과를 UI에 표시할 때

DispatchQueue.main.async를 사용하여 메인 스레드에서 UI를 업데이트 합니다

 

+

메인규에서 다른 큐로 작업을 보낼 때 sync를 사용하면 안된다

만약 sync를 사용하게되면 스레드는 데드락이 됩니다

 

그럼 해당 작업이 끝날 때 까지 기다려야하기 때문에

화면 그리는게 버벅거리게 될겁니다

그래서 반드시 async를 사용해서 보내야합니다

 

그리고 캡처현상때문에 [weak self]를 사용하여 참조카운트를 올리지 않고 안전하게 해제하기 위해서 사용합니다


async, sync, serial, concurrent

 

 

왜 비동기가 필요할까?

 

비동기를 사용하려면 사용자 인터페이스 응답성을 유지하고 성능을 향상시키고 복잡한 작업을 처리하고 오류를 처리하는데 도움이 됩니다

 

import Foundation

func downloadImage(url: URL, completion: @escaping (UIImage?) -> Void) {
    DispatchQueue.global().async {
        let data = try? Data(contentsOf: url)
        let image = data?.image
        DispatchQueue.main.async {
            completion(image)
        }
    }
}

 

이미지 다운로드 메서드를 살펴보면

이미지를 받게되면 콜백함수가 호출됩니다

그래서 다운로드 작업이 UI스레드를 차단하지 않고 백그라운드 스레드에서 수행되고

작업이 완료된 후에 UI를 업데이트 할 수 있는 장점이 있습니다

 

 

import UIKit

class ViewController: UIViewController {
    
    let queue = DispatchQueue.global()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("시작")
        queue.async {
            print("task1")
        }
        queue.async {
            print("task2")
            print("task3")
            print("task4")
        }
        queue.async {
            print("task5")
        }
        
    }
}

 

 

만약 위 코드처럼 작성했다면 사실상

main은 DispatchQueue에 넘기고 자기할걸 하게되니까

사실상 자기가 할 게 없겠죠?

 

 

 


import UIKit

class ViewController: UIViewController {
    
    let queue = DispatchQueue.global()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("시작")
        queue.sync {
            print("task1")
        }
        queue.sync {
            print("task2")
            print("task3")
            print("task4")
        }
        queue.sync {
            print("task5")
        }
    }
}

 


 

 

작업에 대한 시작과 종료에 대한 순서 예측이 가능

 

 

작업 시간은 알아서 시작하겠지만 끝나는 순서는 알 수 없게됨

 

 

순서가 중요하다면 Serial

순서가 중요하지 않고 속도가 중요하다면 Concurrent

 


async, sync, serial, concurrent

Example

import UIKit

class ViewController: UIViewController {
    
    //let queue = DispatchQueue.global()
    let serialQueue = DispatchQueue(label: "com.example.serialQueue")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("시작")
        
        serialQueue.sync {
            print("Task 1 started")
            sleep(2) // Simulate a time-consuming task
            print("Task 1 finished")
        }

        serialQueue.sync {
            print("Task 2 started")
            sleep(1)
            print("Task 2 finished")
        }

        print("All tasks are done")
    }
}

 


import UIKit

class ViewController: UIViewController {
    
    //let queue = DispatchQueue.global()
    let serialQueue = DispatchQueue(label: "com.example.serialQueue")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("시작")
        
        serialQueue.async {
            print("Task 1 started")
            sleep(2)
            print("Task 1 finished")
        }

        serialQueue.async {
            print("Task 2 started")
            sleep(1)
            print("Task 2 finished")
        }

        print("All tasks are queued")
        
    }
}

 


import UIKit

class ViewController: UIViewController {
    
    //let queue = DispatchQueue.global()
    //let serialQueue = DispatchQueue(label: "com.example.serialQueue")
    let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("시작")
        concurrentQueue.sync {
            print("Task 1 started")
            sleep(2)
            print("Task 1 finished")
        }

        concurrentQueue.sync {
            print("Task 2 started")
            sleep(1)
            print("Task 2 finished")
        }

        print("All tasks are done")
       
        
    }
}

 


import UIKit

class ViewController: UIViewController {
    
    //let queue = DispatchQueue.global()
    //let serialQueue = DispatchQueue(label: "com.example.serialQueue")
    let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("시작")
        concurrentQueue.async {
            print("Task 1 started")
            sleep(2)
            print("Task 1 finished")
        }

        concurrentQueue.async {
            print("Task 2 started")
            sleep(1)
            print("Task 2 finished")
        }

        print("All tasks are queued")
       
        
    }
}

 

 


Global Queue

Concurrent 특성을 가지고 있는 Queue이고 Quality Of Services(QoS)에 따라서 종류가 나뉩니다

그리고 우선순위가 높은일에 더 많은 스레드를 배치하여 우선순위(먼저 작업처리)를 둘 수 있습니다

그리고 1-6 순위로 중요도를 가집니다

 

 

userInteractive
사용자와 직접 상호 작용하는 작업에 가장 적합 (UI 업데이트, 애니메이션 등)
가장 높은 우선순위를 가지며, 시스템은 이 작업을 최대한 빠르게 처리하도록 노력
배터리 소모가 가장 많음

// UI 업데이트를 userInteractive 큐에서 수행합니다.
DispatchQueue.global(qos: .userInteractive).async {
    // UI 요소 업데이트
    DispatchQueue.main.async {
        // ...
    }
}


userInitiated
사용자가 시작한 작업에 적합(로컬 데이터베이스 읽기, 파일 열기 등)
userInteractive 다음으로 높은 우선순위를 가지며, 시스템은 이 작업을 빠르게 처리하도록 노력
userInteractive보다 배터리 소모가 적음

// 로컬 데이터베이스를 userInitiated 큐에서 읽습니다.
DispatchQueue.global(qos: .userInitiated).async {
    let data = try? loadLocalData()
    DispatchQueue.main.async {
        // data를 사용하여 UI 업데이트
    }
}


default
일반적인 백그라운드 작업에 적합(네트워킹, 데이터 처리 등)
기본적인 우선순위를 가지며, 시스템은 다른 작업에 영향을 주지 않는 범위 내에서 이 작업을 처리

// 네트워크 요청을 default 큐에서 수행합니다.
DispatchQueue.global().async {
    let url = URL(string: "https://api.example.com/data")!
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        // ...
    }
    task.resume()
}

utility
오랜 시간 실행되는 작업이나 시스템 리소스를 많이 사용하는 작업에 적합 (데이터베이스 백업, 이미지 처리 등)
낮은 우선순위를 가지며, 시스템은 다른 중요한 작업을 위해 리소스를 우선적으로 할당
배터리 소모가 적음

// 이미지 처리를 utility 큐에서 수행합니다.
DispatchQueue.global(qos: .utility).async {
    let image = loadImage(name: "image.png")
    let processedImage = applyFilter(image: image)
    // ...
}


background
사용자에게 인지되지 않는 백그라운드 작업에 적합 (백그라운드 갱신, 위치 추적 등)
가장 낮은 우선순위를 가지며, 시스템은 다른 작업에 영향을 주지 않고 리소스가 남아 있을 때만 이 작업을 처리
배터리 소모가 가장 적음

// 백그라운드 갱신 작업을 background 큐에서 수행합니다.
DispatchQueue.global(qos: .background).async {
    // 백그라운드 갱신 작업 수행
    // ...
}


unspecified
특정 QoS 레벨을 지정하지 않고 작업을 실행
시스템은 작업의 특성을 기반으로 적절한 QoS 레벨을 할당

 

// 특정 QoS 레벨을 지정하지 않고 작업을 실행합니다.
DispatchQueue.global().async {
    // 작업 수행
    // ...
}

 

 

반응형