본문 바로가기
IOS/실험

RunLoop.main vs DispatchQueue.main

by Joahnee 2023. 10. 16.

문제 상황

콤바인을 사용하면서 scheduler를 설정할 때, 평소 Runloop.main과 DispatchQueue.main이 둘 다 Main Thread에서 동작한다는 공통점을 갖기 때문에 UI Update를 진행할 때 두 가지를 혼용해서 사용했었습니다.

 

하지만, 프로젝트를 진행하던 도중 tableView를 스크롤하는데 이미지를 포함하는 Reusable Cell이 한칸씩 변경되지 않고 스크롤이 종료된 시점에 한번에 셀의 이미지가 변경되는 현상이 발생하였습니다. 😂

 

그래서 해당 부분에 대한 공부를 시작했는데요~

 

아래는 해당 현상 설명하기 위한 예제 프로젝트까지 준비되어 있으니 끝까지 잘 봐주세요 🙏

 

문제 해결 과정

처음에는 문제를 찾는데 시간이 걸렸었는데요 ..ㅠ

 

Schduler의 Runloop라는 단어를 보고 아차 싶었답니다.

 

왜 늦게 나오는지에 대해서 대충은 감이 왔거든요...

 

본론으로 들어가서 제가 문제를 해결하기 위해 학습한 개념과 문제를 어떻게 해결했는지 설명드리겠습니다.

.

.

.

우선 Scheduler란?

closure의 실행시기와 방법을 결정할 수 있는 프로토콜 입니다!

 

이걸로 closure의 코드를 빨리 실행 시킬수도, 특정 시간 이후에 실행시킬 수도 있어요

 

한마디로, 작업이 언제 어디서 수행되는지를 정의하는 겁니다.

 

Combine에서는 Publisher가 특정 Scheduler에서 동작할 수 있도록 할 수 있습니다.

.

.

.

Runloop.main이  뭐죠?

Runloop에 대한 설명은 공식문서에도 나타나있는데요 :)

 

Runloop.main은 main thread의 run loop를 리턴해준다고 합니다.

 

한마디로, Main Runloop라는 것입니다.

 

원래 Thread의 RunLoop는 저희가 직접 반복문을 동해서 RunLoop를 반복 실행시켜줘야 하는데요.

 

Main Thread는 UI관련 업무를 담당하는 쓰레드이기 때문에,

애플리케이션이 실행될때 프레임워크 차원에서 자동으로 Main RunLoop를 설정하고 실행해 준다고 합니다.

 

그래서 저희가 따로 Runloop에 대한 실행이나 current로 생성시켜주지 않아도 항상 Runloop.main이 존재했던 겁니다.

.

.

.

DispatchQueue.main은 뭐죠?

Swift에서 운영체제가 스레드를 관리해주고 실행해주는 큐들 중에서

Main Thread를 사용하는 큐라고 생각하시면 될 것 같습니다.

.

.

.

이론적으로 RunLoop.main vs DispatchQueue.main?

Thread 내부에 Runloop가 있는 것이기 때문에 Thread가 Runloop를 포함하는 개념이죠.

 

Main Thread는 Main Runloop를 갖고 있습니다.

 

그렇다면 DispatchQueue.main을 Scheduler로 설정한다면 Main RunLoop와 관계없이,

직접 Main Thread에 작업을 시키는 거겠죠?

 

하지만, RunLoop.main을 Scheduler로 설정한다면?

 

현재 UI에 대한 Input이 들어온다면 해당 Input을 처리하는 RunLoop가 종료될 때 까지 다른 작업을 수행할 수 없을겁니다.

.

.

.

예시를 통한 RunLoop.main vs DispatchQueue.main!

이런 연속적인 UI Input을 주는 방법에는 UIScrollView가 있습니다.

 

저는 UIScrollView의 이벤트를 활용하기 위해 간단하게 TableView로 작업하는 예시 프로젝트를 작성해봤습니다.

 

아래는 DispatchQueue.main와 RunLoop.main을 변경하는 코드 부분입니다.

    func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as? CustomTableViewCell else { return UITableViewCell() }
        image(index: indexPath.row)
            .receive(on: DispatchQueue.main)
            .sink { image in
                guard let image else { return }
                cell.newImageView.image = image
                print("cell변경 - \(indexPath.row)")
            }
            .store(in: &subscription)
        return cell
    }

 

RunLoop.main으로 Cell의 UI를 변경해보자

스크롤을 멈추지 않으면 RunLoop가 종료되지 않아서 RunLoop.main에서 하려는 작업들은

모든 스크롤이 종료되고나서 동시에 실행되게 됩니다.

RunLoop.main

 

DispatchQueue.main으로 Cell의 UI를 변경해보자

다음 셀이 보일때 마다 셀의 이미지를 변경해주는 모습입니다!

 

cell변경 메시지의 순서가 가끔 맞지않는 이유는

이미지 크기가 일정하지 않아서 큰 이미지를 불러올 때 시간이 걸리는 겁니다😅

DispatchQueue.main

 

결론

RunLoop.main과 DispatchQueue.main을 구분해서 사용하자!

 

UIEvent가 모두 종료되고나서 작업을 처리하고 싶다면? RunLoop.main

 

UIEvent를 진행하면서 작업도 처리하고 싶다면? DispatchQueue.main

 

 

예시 프로젝트 코드

- 깃허브 (예제를 위한 코드라는걸 감안해주세요😂)

 

참고

- https://babbab2.tistory.com/68

궁금한  점이 있으시다거나 틀린부분이 있다면 피드백은 언제나 환영입니다🤗

댓글로 알려주시면 감사하겠습니다!! 🙇‍♂️

댓글