안녕하세요 iOS 개발자 루크입니다!
오늘은 AutoLayout 의 그 이면, AutoLayout 은 iOS 내부에서 어떻게 적용되고, iOS 는 어떻게 이를 계산해서 화면을 그리게(render) 하는지 알아보겠습니다
iOS 에서 화면을 그리는 흐름은 Render Loop 라고 부릅니다
아이폰과 아이패드는 60Hz , 아이패드 프로는 120Hz 의 주사율을 갖고 있어요. 즉 아이폰 은 1초에 60 번 화면을 업데이트하고 아이패드 프로는 120번 업데이트 합니다.
1초를 60 과 120 으로 각각 나누면 16.7 밀리초와 8.33 밀리초가 됩니다.
렌더 루프는 이 밀리초 단위로 한 사이클을 돌며 화면을 업데이트하게 되는 거죠.
VSYNC 라는 단어가 등장합니다.
VSYNC 는 앞서 위에서 말한 바와 같이 16.7 밀리초, 8.33 초마다 화면을 정기적으로 업데이트하는 시기를 나타낸다고 이해하시면 됩니다.
다음 VSYNC 전까지 애니메이션이 준비되지 못하면 다음 VSYNC 까지 기다려야 합니다. VSYNC 타이밍에만 화면이 업데이트 되기 때문이죠!
렌더 루프는 크게 세 단계로 나뉘어져 실행되고 이 세단게는 각각 VSYNC 안에 위치해야합니다.
VSYNC 시기에 적절하게 다음 단계로 넘어가지 못하면 그 다음 VSYNC 때 다음 단계로 진입할 수 있으므로 프레임이 한프레임 밀리게 되고 사용자는 화면이 버벅인다고 느낍니다. 이를 hitch 라고 표현하더라고요 :)
앞서서 크게 3단계라고 표현은 드렸지만 세부적으로는 5단계로 나뉠 수 있습니다.
Render Loop 는 크게는 3단계, 세부적으로는 5가지 단계로 이루어져있습니다
App
1.
Event phase : 앱이 화면 변경을 요구하는 이벤트를 받는 단계
2.
Commit phase : 레이아웃을 위치시킴 → render 트리거
Render Server
3.
Render prepare : render 트리거를 받아 render 준비를 하는 단계
4.
Render execute : rendering 실제 화면을 그리는 단계
On the display
5.
Display : 다 그려졌으면 화면에 보여주는 단계
아래의 그림을 화면에 표시하는 예시를 들면서 설명을 같이 드리겠습니다.
1. Event phase
화면 변경을 유발하는 이벤트의 발생 및 이벤트 전달이 이루어지는 단계에요!
Ex. 화면 터치, 네트워킹 요청, 키보드 터치 ,타이머, 네트워크 콜백
등등이 이벤트가 될 수 있습니다.
예시와 함께 볼게요
1.
이벤트 발생
2.
앱이 레이어의 Bounds 값을 업데이트하고자 할 때, 코어 애니메이션이 setNeedsLayout 을 호출
setNeedsLayout 은 모든 레이어를 확인해보며, 레이어가 재연산이 필요한지 점검합니다. (여기서 재연산이 필요한 레이어는 변경이 요구 되는 레이어 입니다)
점검하지 않으면 불필요한 레이어까지 모두 다시 연산해야하니까 아무래도 중복작업을 피하기 위해서는 “필요한 요청” 에 대해서만 재연산을 하는 게 맞겠죠.
예를 들어 아래의 노란색 View 의 위치를 수정해야 한다고 가정하면,
하위 View 들의 위치도 다같이 수정되어야 하니까 노란색으로 칠해진 View들이 필요한 요청이 되겠네요.
여기서 제가 깨달은 점이 하위 View 를 많이 가지고 있는 상위 View에 애니메이션을 주게 되면 레이아웃 연산과정이 무거워지겠다는 점입니다. Hitch 가 App 단계에서 발생한다면 이부분이 원인일 수 있겠네요.
필요한 레이아웃(변경이 있는)에 대한 요청을 하나로 합쳐요.
합친 다음에는 Commit Phase 에서 순서대로 재연산을 실행하죠 ( 중복 작업을 회피하기 위해)
EventPhase 가 끝나면 자동으로 Commit phase 가 시작됩니다.
2. Commit phase
1.
시스템이 재연산이 필요한 모든 레이어를 취합해, 부모 → 자식 순으로 나열합니다.
2.
setNeedDisplay 메서드를 활용해 다음 드로잉 사이클 때, 해당 레이아웃을 그리도록 메시지를 전달합니다.
•
Layout → Draw 로 전달
2.
Bounds 값을 설정
•
각각의 하위 View 들의 Bounds 값을 정해주게 됩니다.
여기서 주의해야할 점은 Bounds 값은 자기 자신을 기준으로 결정하는 자기 자신만의 고유 값이라는 점입니다. (이 단계에서 서로 다른 View 간의 관계는 정해지지 않습니다)
cf. frame (frame 은 superView 를 기준으로 잡은 좌표계)
3.
setNeedsLayout 메서드를 호출해서 하위 뷰들에 대해 레이아웃을 설정함.
•
레이아웃은 각각의 View 들의 관계를 의미합니다.
•
각각의 View 들은 자신이 어디에 위치해야 하는지 알게 됩니다.
만약 View 의 프로퍼티 (이미지 뷰의 이미지, 라벨의 텍스트) 와 같은 비쥬얼적인 업데이트가 필요하다면, 업데이트가 필요한 View 들은 setNeedsDisplay 를 호출해요
레이아웃과 비슷하게 시스템은 이러한 요청을 하나로 합친 후, 모든 레이아웃 작업이 끝나면 이 요청을 수행한다.
Drawing 프로세스
그리고 이제 모든 레이어가 배치되고 그려졌으므로 변경된 레이어 트리 전체가 수집됩니다.
이렇게 변경된 레이어 트리 전체는 렌더링을 위해 Render Server 로 보내집니다.
Render
자, 이제 부터는 Render Server 입니다.
Render Server 에게는 하나의 임무가 있습니다. 그 임무는 바로
“레이어 트리를 실제로 보여지는 이미지로 바꾸는 것"
3. Render prepare
준비단계에서는 Render Server 가 모든 레이어 트리를 순회하며, GPU 가 실행할 수 있는 선형 파이프라인을 준비합니다.
이러한 작업은
•
부모 → 자식 순
•
back → front
의 순으로 진행됩니다.
이렇게 준비된 리니어 파이프라인은 GPU 로 전달됩니다.
4. Render execute
준비를 마친 뒤 그림을 렌더링 합니다.
각각의 레이어는 최종 texture 로 합성됩니다. 어떤 뷰는 렌더링하는 데 오래걸릴 수 있어요.일단 GPU 가 실행되고 이미지를 렌더링하기 시작하면, 다음 VSYNC 에 보여질 준비가 끝납니다.
5. Display
화면에 보여주는 동작이 실행됩니다.
이렇게 앞서서 본 Render Loop 는 병렬적으로 실행됩니다.
VSYNC 마다 화면을 전환해서 보여주게 되는데, 이때까지 앞서 본 작업이 마무리가 되지 않으면, 한프레임 뒤로 밀려서 다음 VSYNC 때 뷰의 변경사항이 보여지게됩니다.
주황색 View 를 렌더 서버에서 렌더링 중임과 동시에 초록색 View 는 Event Phase, Commit phase 가 실행되고 있음을 볼 수 있습니다.
이렇게 여러개의 View 가 병렬적으로 렌더 루프를 타기 떄문에, 하나의 View 를 렌더링할 때 VSYNC 타이밍에 맞추어서 View 가 준비되지 않으면 문제가 생깁니다.
다음 VSYNC 까지 다른 View 가 기다려야 하는 일이 생기는 거죠.
이를 우리는 hitch 라고 부릅니다.
Hitch
1. 커밋 히치
커밋 히치는 커밋 단계가 VSYNC 전에 마무리 되지 않았을 떄 발생하는 hitch 입니다.
빨간색 네모안을 보시면 이해가 쉬울 것 같아요.
Commit phase 가 VSYNC 전에 완료되지 못했고
따라서 다음 VSYNC 까지 기다려야 Render prepare 단계로 진입할 수 있는 거죠.
이 사이에 한프레임이 붕 뜨게 되고, 우리는 이를 버벅임으로 인지하는 겁니다.
이는 심하면 두프레임 이상의 hitch 로 이어질 수 있습니다.
2. Render hitch
렌더링 히치는 render execute 과정에서 렌더링 시간이 길어져 VSYNC 전에 렌더링이 마무리가 되지 못해 발생하는 히치입니다.
iOS 개발자가 렌더 루프를 이해하게 되면, 애니메이션의 버벅임 현상의 원인을 이해할 수 있게 되는 것이고,
이해할 수 있다면 이를 적절히 평가하고 해결할 수도 있게 됩니다.
hitch 를 측정하고 해결하는 방법은 다음 포스팅에서 다루어보도록 하겠습니다
이번 포스팅은 여기서 마무리할게요! 안녕!