요약
뷰의 드로잉 사이클에는 크게 3가지가 있습니다. 먼저 Constraints(제약)을 업데이트하고 재배치하고 다시 뷰를 그리는 방식입니다. 구체적인 메서드로는 오토레이아웃을 업데이트 하는 updateConstraints(), 하위 뷰의 레이아웃 위치와 크기를 재조정하는 layoutSubviews(), 색상/이미지/텍스트 등 실제 내부 컨텐츠를 다시 그리는 draw() 메서드가 있습니다.,
부가설명
app이 실행되면 iOS의 UIApplication이 Main Thred에서 main run loop를 실행시킨다. 메인런루프는 화살표대로 한바퀴씩 돌며 이벤트를 처리한다. 뷰를 업데이트 하는 사이클은 메인 런루프에서 발생한다고 볼 수 있다.
메인 런루프는 3.5ghz로 1초에 35억번의 일을 처리한다. 마지막 업데이트 사이클에서는 아이폰 화면 주사율이 60hz라고 했을 때 화면은 1초에 60번 메인스레드에서 그리게 된다. 물론 화면을 매번 1초에 60번씩 그리는 것은 아니고 뷰에 변경사항이 있을 때만 다시 그리게된다. 뷰의 변경사항이 있어야만 다시 그린다면 뷰의 변경사항이 있는 특정 시점을 어떻게 판단할까?
공식문서에서는 위와같은 4가지 상황을 "특정시점"으로 판단하고 뷰를 다시 그린다고 한다. 특정시점이 아닌 경우에는 스냅샷을 캡쳐해서 비트맵 컨텍스트로 저장해둔후 보여준다. 그렇다면 특정 시점에서 마지막 직접 개발자가 업데이트 요청하는 경우에서는 어떻게 요청할 수 있을까?
먼저 알아둘 지식은 뷰에게 언제 커스텀하게 요청해야하는 것이다. 요청시점도 갓-플답게 세분화되어있다. 하나의 메서드로 뷰 재드로잉을 퉁치는 게 아니라 setNeedsDisplay() 에 정보를 담아두다 마지막 draw() 메서드 호출시점에서 뷰가 다시 그려진다.
한 발자국 더 나아가아가. 뷰를 직접 그리는 건 사실 런루프가 아니다. 런루프는 이벤트를 처리하고 특정시점을 인지하는거지 진짜로 뷰를 그리는건 OS 단계에서 이루어진다.( Core Animation, Metal 또는 OpenGL 같은 그래픽 프레임워크와 관련) 이러한 뷰를 그리는 단계를 Render Loop 라고 한다. 개발자가 커스터마이징하는 요청은 메서드를 통해 RunLoop와 RenderLoop를 이어주는 것이다. 따라서 RenderLoop 시점에 따라 할 수 있는 요청도 세분화된다.
Render Loop는 총 3단계로 구분된다. 바로 Update Constraints, Layout, Display 단계들이다. Update Constraints 에서는 제약을 업데이트한다. Layout에서는 업데이트된 제약을 통해 알맞은 곳에 위치시켜준다. Display는 위치시킨 뷰를 화면에 보여주는 단계이다. 구체적인 순서는 위와 같다. 제약을 업데이트 할 때는 가장 하위뷰부터 상위뷰 순서대로 제약을 업데이트한다. 제약에 맞는 위치는 상위뷰부터 하위뷰 순서대로 위치시킨다(당연한 이야기이긴하다. 상위뷰 위치도 모르는데 하위뷰 위치를 잡을 순 없기 떄문에). 위치까지 맞췄으면 이제 상위뷰부터 하위뷰로 내려오면서 그려낸다.
각 Phase 마다 호출되는 메서드들이다. 같은 가로줄에 있는 메서드들의 의미는 역할만 다를 뿐 하는 일은 같다. 앞서 draw 메서드가 찐으로 뷰를 그리는 메서드라고 했다. 따라서 updateConstraints() 메서드는 찐으로 제약을 업데이트하고 layoutSubview() 는 찐으로 뷰들을 위치시킨다. 각 phase마다 막타 치는 녀석들이라고 볼 수 있다. 마지막 줄 updateConstraintsIfNeeded() 와 layoutIfNeeded() 메서드들의 경우에는 호출하자마자 즉각적으로 각각 제약을 업데이트하고 위치잡기를 업데이트한다.
좀 더 깊이 들어가보자. UpdateConstraints 는 어떻게 동작하는걸까? 먼저 우리는 윈도우 안에 뷰가 있고 뷰 안에 Constraint 정보가 있다는 것을 알고 있다. 그렇다면 제약정보만 가지고 어떻게 위치를 시킬까? 바로 Equation 덕분이다. Equiation은 위 빨간 박스처럼 위치값을 산출할 수 있는 방정식이다.
엔진은 방정식에 따라 뷰의 제약정보를 가지고 요리조리 요리한 후에 완성시킨 후 Variable 정보로 view에 전달한다.
다음에 엔진은 뷰에다 값 바뀌었어~ 빨리 위치다시잡아라고 트리거링을 한다. (이때 가장 상위뷰에 전달한다. 그래서 위치잡는 순서가 상위뷰 -> 하위뷰로 이어진다)
앞서서 제약값이 생기면 엔진이 방정식에 따라 계산해준다는 것을 우리는 배웠다. 그렇다면 우리가 하지 말아야할 행동은 분명하다. updateConstraints() 메서드 안에서 제약을 deactivate했다가 activate 시켜서는 안된다. 비활성화했더라도 엔진에서는 방정식을 돌려서 값을 산출하는 잉여작업이 계속 일어나기 때문이다. 차라리 nil값을 넣었다가 제약값을 넣어주어야한다. (nil이면 계산을 안하니까) 이렇게 엔진에게 불필요한 방정식 계산을 하게 만드는 걸 WWDC에서는 Churning (휘젓다) 이라고 표현한다.
구체적인 호출 순서는 위와 같다. 초록색은 뷰컨 라이프사이클이고 파란색은 뷰 드로잉 사이클이다.
'Flutter' 카테고리의 다른 글
Flutter Widget의 작동원리 (0) | 2024.05.10 |
---|---|
| Combine | 4. Filtering Operators (0) | 2024.05.09 |
| Combine | 3. Transforming Operators (0) | 2024.05.08 |
| Combine | 2. Operators and Subjects (0) | 2024.05.07 |
ViewController의 생명주기 (0) | 2024.05.06 |