https://medium.com/@duwei199714/ios-why-the-ui-need-to-be-updated-on-main-thread-fd0fef070e7f
해당 글을 번역한 것입니다.
개발하면서 imageView.image = asImage
혹은 UIApplication.sharedApplication
와 같은 UIKit Components를 Background Thread에서 호출할지도 모른다.
이렇게 코드를 치면 Runtime error가 발생하고 즉각 고쳐야 한다.
신중하게 생각해보면 왜 메인 쓰레드에서 UI를 업데이트 해야될까?
UI를 background 쓰레드에서 업데이트된다면 무슨일이 발생할까? 메인 쓰레드내에서 blocking을 막기 위해 background 쓰레드에서 UI를 업데이트하는게 더 좋지 않을까?
UIKit내의 대부분의 Components들은 nonatomic
하며 이것은 thread safe하지 않는다. 모든 프로퍼티가 thread-safe하도록 설계되는 것은 매우 비현실적이다. 그렇게 되면 framwork가 매우 비대해진다.
thread-safe한 framework으로 설계하는 것은 nonatomic
에서 atomic
하게 만들고 NSLock
또한 처리해야 한다.
will be removed view
가 tap된다면 어떠한 이벤트에 반응해야될까? 어느 쓰레드가 반응해야될까?위 사항을 봤을 때 background thread에서 실행했을 때 큰 이점이 없어보인다. 우리가 이 문제를 해결하려고 접근을 해보면, "Serial Queue로 이것들을 다루면 문제가 발생하지 않을 것이다"라고 쉽게 결론을 내릴 수 있다. 그래서 UI는 main thread에서 동기적으로 동작될 필요가 있다.
Apple측에서 thread-safe하지 않도록 하는 것은 의도된 설계이다. thread-safe하게 만드는 것은 성능적인 면에서 이점이 없다. 이것은 사실 더 느리게 만든다. UIKit이 메인스레드에서만 작동한다는 사실은 프로그램을 동시적으로 만들기 더 쉽다. 당신이 해야할 것은 UIKit이 메인스레드에서만 작동하도록 하면 된다.
지금 UIKit을 리팩토링하고 위에서 발생한 문제를 해결했다고 가정을 하자. 그렇게 되면 UI를 Background thread에서 업데이트할 수 있을까?
UIApplication
은 메인스레드에서 Runloop(Main RunLoop)를 시작할 것이다. 이것은 어플리케이션 주기(Application lift time)동안에 대부분의 유저 이벤트를 다룬다. 사용자 이벤트에 가능한 빨리 응답할 수 있도록 이벤트를 지속적으로 처리하고 최대 절전 모드를 사용하는 루프에 있다. 스크린이 refresh할 수 있는 이유는 'Main Runloop'이 실행중이기 때문이다.(driving)
또한 모든 뷰의 변화가 즉각적으로 이뤄지지 않고 뷰들은 현재 Runloop의 끝에 다시 그린다(redraw). application은 모든 뷰의 변화를 다룰 수 있고 모든 변화는 동시에 active된다. 이것을 "View Drawing Cycle"이라고 부른다.
Magical UIKit을 우리가 사용하고 Background threads에서 UI가 업데이트 된다고 가정해보자. 문제는 우리가 device를 회전시키고 layout을 업데이트할 때 발생한다. 각 쓰레드의 고유한 Runloop이 있기 때문이다. 그래서 모든 변화는 동시에 적용될 수 없고 디바이스가 회전된 이후에 몇몇 뷰의 loyout은 업데이트되지 않는다.
또한 Magical UIKit이 메인스레드에서 있지 않기 때문에 Main Runloop
내에 있는 유저 이벤트가 동기적으로 보여지지 않는다.
만약 문제를 동기적으로 처리를 한다면 Background Thread에서 처리할 수 있을까?
Core Animation은 4가지 스텝으로 나눠진 렌더링을 하기 위해 Core Animation Pipeline을 사용한다.
Core Animation Pipeline내에서, 우리는 1/60s에서 작업 준비를 마치고 render서버로 보내서 1/60s내에 렌더링을 끝낸다. 이 방식으로 앱은 멈추지 않는다(not stuck)
그러나 Magical UIKit을 사용한다면, 많은 Background Thread가 UI를 업데이트 하고 Runloop의 막바지에 screen에 rendering해야될 때 문제가 발생한다.
각 스레드의 커밋은 다른 render 정보를 가지고 있어서 우리는 더 많은 Commit Transactions 다뤄야 하며 Core Animation Pipeline은 정보를 항상 GPU에 커밋합니다. 그러나 렌더링은 사실 매우 비싼 시스템 자원(video memory, CPU)의 매우 비싼 작업이며, 스레드와 많은 수의 Transaction 간의 빈번한 Context Switching은 GPU를 처리할 수 없게 만들고, 이는 성능에 영향을 미쳐 레이어 트리 제출을 1/60초 내에 완료할 수 없게 만든다.