본문 바로가기
🍎 iOS/DevNote

[iOS] UI는 왜 MainThread에서만 동작할까?

by @Eddy 2023. 2. 26.
728x90

궁금증이 발생한 이유

궁금증이 시작된 부분

DispatchQueue를 통해 처음으로 Concurrency(동시성) 작업을 했을 때, 맞닥뜨렸던 궁금증이다.

당시 의문은, 왜 global은 안 되고, main은 되는데!! 였다.

 

결론은 질문처럼, UI는 MainThread에서만 처리되어야 한다는 것이고,

알아본 과정을 아래 정리했다.

 

먼저, UIKit은 Thread-safe하지 않다는 것으로 시작한다.

Thread-safe하다는 것은, Multi-Thread Programming에서 shared resource에 여러 쓰레드가 동시에 접근해도, 프로그램 실행에 문제가 발생하지 않는다는 것을 말하는데, Multi-Thread를 지원하는 iOS가 Thread-safe하지 않다? 그럼 문제가 생길 여지가 있다는 얘기가 아닌가..?

 

UI를 다루는 UIKit이 Thread-safe하지 않다는 것은, 비동기 처리가 불가능하다는 것을 말한다. 왜냐, atomic(독립성)을 갖추지 않기 때문에.

UIKit은 non-atomic(비독립성, 서로 연결된)의 성격을 띈다. 데이터베이스 트랜잭션을 안전하게 수행하기 위한 ACID를 지키지 않은 이유는 이를 지키기엔 UIKit이 너무 방대해 오히려 성능 저하가 발생하기 때문이라고 한다.

 

또한 Thread-safe하다는 것은 UI를 비동기적으로 처리할 수 있다는 말이 되고, 이는 당연하게도 Thread-safe하지 않은 UIKit에서 UI를 비동기 처리하려하면 에러를 발생시킨다.

편의상 string으로 표현

왜냐하면, UI를 삭제, 추가, 수정을 비동기처리했을 때, 먼저 처리되는 게 어떤 것인지 예측하기 어렵다.

만약 추가 -> 삭제 -> 수정 순으로 처리되면, 이미 삭제된 UI를 수정하라는 내용을 전달받게 되므로, 에러가 발생하게 된다.

그래서 UI는 동기처리가 되어야 한다.

(위에서 말하는 MainQueue를 MainThread와 같다고 보면 곤란하다. 단지, MainQueue는 MainThread에서 동작하는 Queue라고만 이해하면 좋을 것 같다.)

 

이를 Run Loop와 View Drawing Cycle로 다시 설명하면,

Run Loop는 사용자 이벤트(삭제, 추가, 수정)를 처리하고, (조금 있다 추가 설명)

View Drawing Cycle은 Main Thread에서 Main Run Loop에 의해 변경된 사항을 한번에 활성화하는 것을 말한다.

 

오류가 발생하는 이유는 알겠는데,

애플이 말하는 성능적 문제가 발생하는 이유는 왜 때문일까?

아래 이미지를 한번 보자.

Core Animation 애플 공식문서 이미지

UIKit과 CoreAnimation, CoreGraphics, Metal(WWDC 2018 OpenGL ES는 Deprecated), Graphics Hardware로 구분된 것을 알 수 있다.

UIKit은 모든 Component를 갖고, User event를 처리하지만, 렌더링 코드를 포함하진 않는다.

CoreAnimation은 View를 그리고, 디스플레이 보여주고, 애니메이션을 담당한다.

 

Core Animation Pipeline

즉, View는 UIKit이 아닌 Core Animation Framework을 통해 사용자에게 보여진다는 것을 의미한다.

CoreAnimation은 CoreAnimation Pipe Line을 사용해 표현하는데, 

Commit Transaction / Render Server / GPU / Display로 나뉜다.

Commit Transaction - 뷰를 배치하고 이미지 디코딩, 포맷 변환 작업 등을 처리 후, 뷰 레이어를 채워 Render Server로 전송

Render Server - Commit Transaction 및 Deserialization에서 전송된 패키지를 렌더링 트리로 분석

GPU - 화면의 VSync(수직 동기화) Signal신호를 기다린 뒤, Metal 렌더링 파이브라인을 사용해 렌더링 후 출력은 버퍼로 전송.

Display - 버퍼에서 데이터를 가져와서 화면에 전송해 표시.

 

보다시피, 16.67ms(=1/60초) 이후 렌더링 서버로 데이터를 보내고,

1/60초 후에 렌더링을 끝내 디스플레이에 보여질 준비를 마친다.

그래야 앱이 멈추지 않는데!

 

UI를 Thread-safe하게, 즉 비동기적인 처리를 가능하게 된다면,

각각의 UI마다 Background Thread에서 Run Loop가 끝나고 화면을 렌더링할 때 문제가 발생한다.

각 Thread가 서로 다른 렌더링 정보를 커밋해 더 많은 Commit Transaction을 처리해야하니까,

Core Animation Pipeline은 항상 GPU에 정보를 커밋하는데,

렌더링은 시스템 비용이 상당해서 Thread와 여러 트랜잭션 간에 빈번한 컨텍스트 전환으로, GPU를 처리할 수 없어 성능에 영향을 미쳐 UI를 보여주지 못하게 된다.

 

그래서 Main Run Loop에서 한번에 처리하는 게 효율적인 것이다.

UIApplication이 Main Thread에서 Main Run Loop를 초기화하고 앱 생명주기동안 대부분의 사용자 이벤트를 처리한다.

(Run Loop는 모든 Thread에 있어서 별도로 명시하지 않아도 되지만, Secondary Thread일 때엔 명시해 실행해줘야한다. 앱 프레임워크는 앱 실행 프로세스의 일부라서, Main Thread에서 Run Loop를 자동으로 설정 및 실행해주고 있다.)

사용자 이벤트를 빠르게 응답하도록 보장하기 위해 지속적으로 이벤트를 처리하고 절전(?)상태가 되는 것이 Loop에 있다. 모든 뷰의 변경 사항은 Run Loop가 끝날 때 다시 그려진다.

 

여기까지가 애플이 에러 가능성이 있고, 성능 저하를 일으키는 Thread-safe한 방식이 아닌 지금의 UIKit을 선택한 이유이다.

 

**요약**

1. UI는 Main Thread에서 작업되어져야 한다.

2. 왜냐하면, UI는 UIKit에 포함되어 있고, UIKit은 Thread-safe하지 않기 때문이다.

3. 하지만, UIKit은 UIComponent를 갖고 있을 뿐, 실제 보여지는 부분은 아니다.

4. UIKit이 Thread-safe하다면, Background-Thread에서 처리가 된다는 것이고,

5. 동시에 처리된 UI가 Core Animation pipeline에서 수많은 렌더링을 발생시킨다.

6. 렌더링을 처리하는 GPU는 1/30초 후에 렌더링을 마치고 디스플레이를 보여줘야 하지만, 너무 오랜 시간이 걸려서 실패!

7. 따라서 Main Thread의 Main Loop에서 한방에 처리하는게 효율적이다.

 

**참고자료**

https://medium.com/@duwei199714/ios-why-the-ui-need-to-be-updated-on-main-thread-fd0fef070e7f

https://www.objc.io/issues/2-concurrency/thread-safe-class-design/

https://ko.wikipedia.org/wiki/데이터베이스_트랜잭션

https://ko.wikipedia.org/wiki/ACID

https://gipple.tistory.com/19

https://minosaekki.tistory.com/16

https://wooono.tistory.com/523

https://gwangyonglee.tistory.com/47

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW24

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html#//apple_ref/doc/uid/TP40004514-CH13-SW12

https://zeddios.tistory.com/932

반응형

댓글