Flutter Widget Rendering
Flutter widget의 렌더링 순서는 위와같다. 1. 먼저 사용자의 인풋을 받아들이고 2. 시간에 따라 UI를 움직이고, 3. 스크린에 위젯을 만들고 4. 스크린 위에서 위치를 잡고, 5. 위치잡은 위젯의 직접적인 내부 구현물을 채우고, 6. 위치와 내부가 채워진 위젯들의 겹쳐진 순서를 ordering 한 뒤에 7. 지금까지 한 작업들을 GPU가 이해할 수 있는 명령어로 바꾸어 실제 화면에 렌더링을 한다.
렌더링 과정은 크게 렌더링 파이프라인과 Graphics 파이프라인으로 나뉘고 각각 다른 스레드를 사용한다. Rendering Pipeline 은 UIThread에서 실행되며 총 5단계 phase로 나누어진다. (위에 나온2,3,4,5,6) Graphic pipeline은 GPU thread 에서 실행되며 마지막 Rasterize 단계를 맡게된다.
렌더링 파이프 라인 안에서 Widget Tree -> Element Tree -> Render Object Tree 를 만들어 최종적으로 Layer Tree를 만들게 된다. 여기서 Layer Tree는 UI를 어떻게 그릴 것인가에 대해 최적화된 정보를 가지고 있게 된다. Raster 단계에서는 전달반은 Layer tree를 픽셀로 변환하고 각 Layer 를 Overlay 하는 과정을 거친다.
Widget Tree, Element Tree, Render Tree
위젯 렌더링 단계 중 하나였던 빌드 단계는 다트 코드 내에서 build 메서드 호출때 실행된다. 이후부터 빌드 메서드 아래에 위젯트리 구조대로 만들기 시작한다. 하지만 위젯트리에서는 위젯의 구조와 레이아웃을 정의하지만 실제 화면에 어떻게 그려질지에 대한 정보는 포함하고 있지 않다. 결국 위젯트리는 어떻게 그려질지 청사진 역할만 하게된다.
위젯트리라는 청사진을 가지고 실제 어떻게 화면을 그릴 것인가에 대한 정보는 Render Object가 가지고 있다. 이러한 RenderObject가 위 렌더링 단계의 3번, 4번 위치잡기와 내용 채우기를 맡게된다.
청사진 역할을 하는 위젯트리와 실제 어떻게 화면을 그릴지 결정하는 RenderObject 사이에는 Element Tree가 있다. Element 객체는 각 위젯은 1대1로 element 객체에 맵핑되고 element객체는 위젯의 상태, 위치, 부모자식간의 관계, 생명주기 등을 관리하게 된다. 실제 코드를 보면 각 위젯은 위와같이 Element 객체를 가지도록 설정되어 있다.
정리하자면 위젯트리는 청사진 역할을 하며 UI구조, 스타일링 정보를 가지고 있고 Element Tree는 맵핑된 위젯의 라이프 사이클, 다른(부모/자식) 위젯간 구성관계를 정의하는 정보를 담게된다. Render Object 는 위젯트리와 Element 위젯을 이용해서 크기,위치,채우기의 역할을 수행한다.
Widget의 생명주기: 생성부터 소멸까지
StatlessWidget은 별다른 상태없이 생성과 소멸만을 반복한다. 하지만 StatefulWidget 같은 경우에는 state 객체에 따라 여러가지 상태를 거친다.예를 들어 setState() 를 호출하면 위젯을 다시 그리게된다. Constructor 단계부터 자세히 들여다보자
Constructor 단계는 위와 같은 단계로 구성된다. 먼저 호출되는 CreateState()는 위젯의 state 객체를 생성후 반환되며 위젯의 생명주기중 단 한 번만 호출된다. state객체는 위젯의 생명주기동안 지속되며 위젯의 상태를 관리한다.
mounted 단계에서는 현재 위젯트리에 올라가있는지 확인한다. 해당 위젯이 위젯트리에 포함되어 있는지 확인한다고 볼 수 있다
initState() 단계에서는 mounted=true가 된것을 확인한 이후 리소스 초기화, 데이터 fetch 같은 초기화 로직에 사용된다. 마찬가지로 위젯의 생명주기중 단 한 번만 호출된다. Build 메서드 이전에 실행되므로 초기상태설정은 이곳에서 해준다.
마지막 didChangeDependencies() 에서는 의존객체가 변경될 때마다 호출되어 의존성/데이터를 초기화하거나 재구성할 때 쓰인다.
Constructor 단계를 지나 이번에는 Build 단계를 자세히 들여다보자. Build 전후로 dirty flag가 있다. 이는 현재 위젯이나 렌더링 오브젝트의 상태를 나타내는 flag로 true면 위젯의 UI업데이트가 필요한 상태, false 이면 위젯이 최신상태임을 나타낸다.
build() 메서드가 호출되면 항상 새로운 위젯 인스턴스를 반환한다. 위젯의 상태가 변경되거나 부모 위젯에 의한 데이터 변경등이 발생할 때 호출된다.
setState() 메서드는 UI갱신을 위한 메서드로 dirty를 true로 바꾸고 build()메서드를 호출하게된다. UI업데이트를 할 일이 있다면 setState() 메서드를 호출하자
didupdateWidget() 도 setState() 와 마찬가지로 UI업데이트 메서드이다. 두 메서드의 차이를 알아보자면 setState() 메서드는 위젯의 상태가 변경될 때 사용된다. 주로 로컬 위젯 상태가 변경되었을 때 사용되며, 이 변경이 UI에 반영되어야 할 때 적합하다.
didUpdateWidget()는 StatefulWidget의 상태 객체에서 호출되어 위젯의 구성이 변경된 후, 즉 위젯의 생성자로 전달된 파라미터가 변경됐을 때 호출된다. 주로 부모 위젯에서 전달된 데이터가 변경되었을 때 필요한 로직을 수행하는 데 사용된다.
정리하자면 setState()는 주로 위젯 내부 상태의 변화에 반응하여 UI를 업데이트할 때, didUpdateWidget()는 위젯의 구성 변경에 반응하고, 필요한 경우 상태를 업데이트하여 UI 변화를 반영하는 데 사용된다.
Constructor와 Build를 거쳐 Dispose() 단계를 자세히 살펴보자. 가장 먼저 deactive() 메서드가 호출된다. 위젯이 위젯트리에서 제거될 때 호출되기 때문에 리소스나 데이터를 정리할 때 사용된다.
일반적으로 위젯을 제거할 떄 Dispose()를 쓰게된다. iOS의 deinit과 유사한 메서드라 볼 수 있다.
Build Context의 역할
모든 위젯은 BuildContext가 포함된 빌드 블록 안에서 진행된다.
이전 내용을 되짚어보면 위젯 자체만으로는 위젯트리 내에서 특정 인스턴스 위치에 대해 알기 어려워서 Element Tree를 통해 위젯간의 위계를 알 수 있었다. 이때 자세히보면 Element 객체는 BuildContext 추상화 클래스의 구현체이다. 따라서 BuildContext는 일종의 인터페이스라고 볼 수 있다. 즉 Element의 역할 수행을 BuildContext 인터페이스를 통해 수행하게 설계되어있다.
다른 프레임워크와 마찬가지로 결국 의존성 관리가 문제이다. BuildContext는 위젯간 계층 의존을 바꿀 수 있기때문에 하지말아야할 사항이있다. 바로 전역적인 BuildContext사용, 비동기작업에서 BuildContext 사용, BuildContext를 initState사용(의존성 설정전에 데이터 가져오면.. 흠)과 같은 사항이다.
https://fastcampus.co.kr/dev_online_flutternative
'Flutter' 카테고리의 다른 글
| Combine | 6. Timing Operators (0) | 2024.05.12 |
---|---|
| Combine | 5. Combining Operators (0) | 2024.05.11 |
| Combine | 4. Filtering Operators (0) | 2024.05.09 |
UIView 의 Drawing Cycle (0) | 2024.05.08 |
| Combine | 3. Transforming Operators (0) | 2024.05.08 |