개발공방

Swift DispatchQueue 기본 원리 본문

Swift/UIKit

Swift DispatchQueue 기본 원리

chemi_ 2022. 7. 14. 22:26

제가 이해한 내용을 이해하기 쉽게 정리합니다.

 

혼잣말하는 느낌으로 작성해서 반말로 작성했습니다 !

 

 

DispatchQueue를 사용해야하는 이유는 도대체 뭘까??

-> 비동기적으로 처리해야할 데이터(API 통신) 가 있을 때 사용한다.

(우리가 어떤 앱을 실행했을때 바로 뭔가가 뜨지않고 1~2초후에 뜨는게 API통신으로 어떤 데이터를 불러오기 때문이다.)

 

비동기적이란 ??

 

순차적(직렬적)으로 진행되는 작업이 아닌 병렬적으로 진행되는 작업

 

이게 무슨 말이지 ??

 

1번(2초 걸리는 작업)

2번(3초 걸리는 작업)

3번(1초 걸리는 작업)

4번(2초 걸리는 작업)

으로 수행되어야할 작업이 있을 때

 

동기(Sync) : 1번이 끝나야 2번이 실행되고, 2번이 끝나야 3번이 실행되고 ... 그럼 총 8초가 걸릴것이다.

 

비동기(Async) : 1번이 끝나던 말던 2,3,4번 작업을 동시에 시작함

 

그럼 비동기적은 어떻게 1,2,3,4번 작업을 동시에 처리할까??

 

이때 나오는 개념이 쓰레드(Thread) 이다.

https://developer.apple.com/documentation/foundation/thread

(별도의 코드 작성없이 뭔가를 실행한다면 default는 메인 쓰레드이다)

 

동기(Sync)는 하나의 쓰레드에서 작업을 하기 때문에 순차적으로 실행하지만, 비동기(Async)는 여러개의 쓰레드로 뿌려서 작업한다.

 

실제로 MainThread에서 딜레이가 있는 함수를 실행했을때 함수가 실행되는 과정에서 이후의 코드가 실행되지 않을 뿐더러, UI가 멈추는 현상이 발생한다.

(UICollectionView를 만들고 테스트해보면 바로 알 수 있음, 밑에 GIF 예시가 있다)

 

이런 현상을 피하기 위해 딜레이가 있는 액션들을 다른 쓰레드로 따로 분리해야한다.

 

거기서 나오는 개념이 DispatchQueue이다.

 

우리가 코드로 만나는 건 DispatchQueue.main  DispatchQueue.global()이 있다.

 

Queue란 ?

 

https://velog.io/@sangh00n/큐queue의-이해

하나가 들어가고 하나가 나오는 방식(FIFO- First In First Out)이라 순차적으로 실행됨.

(가장 먼저 들어간 값이 가장 먼저 나옴. 매표소에 길게 줄서있는 상황을 생각하면 이해가 편할 것 같음)

 

일반적으로 코드를 작성하면 MainThread에 실행되기 때문에 모든 코드들이 직렬적으로 실행된다.

 

왜냐 MainThread는 직렬방식으로 실행되기 때문이다. 

 

실제로 XCode에서 UIKit과 SwiftUI같은 UI적 코드들은 메인 쓰레드에서만 수행되어야 한다.

(여태 DispatchQueue를 사용하지 않았고, 에러가 나지 않았던 이유는 항상 MainThread였기 때문일 것이다)

 

MainThread에서 동기적으로 처리되는 작업이 들어오면 실제로 UI가 멈춘다.

 

버튼을 누르고 스크롤 하면 스크롤이 되지 않는다

메인쓰레드에서 동기적으로 처리되는 작업이 추가되서 그 작업이 끝나는 동안 UI가 멈춰버린것이다.

 

이 문제를 해결하기 위해 DispatchQueue.global() 이 나온다.

 

DispatchQueue.global의 개념은 MainThread가 아닌 서브 Thread로 이해하면 편할 것 같다.

 

main은 말그대로 메인 쓰레드고, global은 메인쓰레드 이외의 다른 쓰레드들을 포함한다.

 

위의 GIF에서 버튼을 눌렀을 때의 작업들을 DispatchQueue.global()로 빼서 작업을 해줘야 한다.

 

    private lazy var testImage: UIImageView = {
        let view = UIImageView()
        return view
    }()
    
    //...

    private func loadImage(from imageUrl: String) -> Void {
        DispatchQueue.global(qos: .default).async {
            guard let url = URL(string: imageUrl) else { return }
            guard let data = try? Data(contentsOf: url) else { return }
            guard let image = UIImage(data: data) else { return }
            self.testImage.image = image
        }
    }

이렇게 작성할 수 있는데,

 

여기서의 문제는 self.testImage.image = image 의 코드는 UI에 관여하는 코드라서 MainThread에서 실행되어야 한다.

 

그래서 앱이 박살나거나, 보라색 친구를 만나게 될 것이다.

 

    private lazy var testUIImage = UIImage() {
        didSet {
            DispatchQueue.main.async {
                self.testImage.image = self.testUIImage
            }
        }
    }
    
    // ...
    
    private lazy var testImage = UIImageView()
    
    // ...

    private func loadImage(from imageUrl: String) -> Void {
        DispatchQueue.global(qos: .default).async {
            guard let url = URL(string: imageUrl) else { return }
            guard let data = try? Data(contentsOf: url) else { return }
            guard let image = UIImage(data: data) else { return }
            self.testUIImage = image
        }
    }

 

이미지를 적용하는데엔 여러 방법이 있겠지만, 난 메인 쓰레드와 구분하기 위해 didSet을 통해 처리를 했다.

 

코드에서 보면 DispatchQueue.global(qos: .default) 가 있다.

 

qos가 뭘까 ??

 

QoS : Quality of Service

 

그냥 우선순위를 설정한다고 생각하면 이해가 쉬울 것 같다.

 

메인 쓰레드는 말그대로 메인이기 때문에 무조건 우선순위가 1번인데, 이외의 쓰레드들은 우선순위를 설정해줘야한다.

 

아래로 갈 수록 우선순위가 낮음 (userInteractive > unspecified)

https://developer.apple.com/documentation/dispatch/dispatchqos

 

각자가 생각하는 우선순위에 맞게 설정해주면 된다.

 

버튼을 눌러도 UI가 멈추지 않는다

 

 

이해가 안되는 부분이나 잘못된 정보가 있다면 댓글로 알려주세요 !

 

 

 

 

 

전체코드 : https://github.com/MMMIIIN/UIKit_DevNote/blob/develop/UIKit_DevNote/Screens/DispatchQueue/DispatchQueueExampleViewController.swift 

 

GitHub - MMMIIIN/UIKit_DevNote: Chemi's UIKit DevNote

Chemi's UIKit DevNote. Contribute to MMMIIIN/UIKit_DevNote development by creating an account on GitHub.

github.com

DispatchCell 코드 : https://github.com/MMMIIIN/UIKit_DevNote/blob/develop/UIKit_DevNote/Screens/DispatchQueue/Cell/DispatchCell.swift

 

Comments