지금까지 StatelessWidget과 StatefulWidget이 어떤 상황에 따라 쓰이는지 알아봤는데요. 오늘은 StatefulWidget이 리빌드 하기 위해 정보를 담는 State에 대해 알아보겠습니다. 아래는 플러터 팀에서 2021년에 소개한 State에 대한 설명 동영상입니다.
영상에서는 State를 제 시간에 특정 지점에서 UI 생성에 필요한 모든 데이터라고 정의하는군요!
State의 생명주기는 enum 타입의 4가지로 분류합니다.
- created : [State] 객체가 생성될때 입니다. 이 때 [State.initState]가 호출됩니다.
- initialized : [State.initState] 메소드가 호출되지만 [State]는 아직 빌드할 준비가 안 됐습니다. 그래서 이때 [State.didChangeDependencies]를 호출합니다.
- ready : [State]가 빌드할 준비가 됐습니다. 아직 [State.dispose]는 호출되지 않았습니다.
- defunct : [State.dispose]가 호출됐고, 이 [State] 객체는 더이상 빌드할 수 없습니다.
enum _StateLifecycle {
created,
initialized,
ready,
defunct,
}
위에서부터 차례대로 라이프사이클을 그리는데요.
1. State.iniState 메소드를 호출하면 created 상태가 되고,
2. 바로 initialized 상태로 변경되면서 State.didChangeDependencies 메소드를 실행합니다. initialized때 정보들을 가지고 위젯들에게 반영해주는 거라 볼 수 있겠습니다.
3. ready상태일 때는 위젯이 모두 빌드가 된 것이고,
4. State.dispose 이후에는 더이상 사용할 수 없는 State가 되었다고 보는거죠.
State의 생명주기 동안 플러터 프레임워크는 무슨 일이 하는지 자세하게 알아보겠습니다.

요약.
[StatefulWidget.createState] 호출 -> [State]가 mounted 됨 -> 프레임워크가 [initState] 호출 -> 프레임워크가 [didChangeDependencies] 호출 -> [State] 완전 초기화 됨 -> [State]의 데이터가 바뀌면 [setState] 호출로 리빌드 요청 -> 부모 위젯이 [didUpdateWidget]메소드 호출 -> build 메소드 호출 -> 서브트리에서 지워지면 프레임워크가 [deactive] 호출 -> [State]가 [dispose] 호출 -> [State]가 unmounted됨
1. 프레임워크는 [StatefulWidget.createState] 메소드를 통해 [State] 객체를 생성합니다.
2. 새롭게 생성된 [State] 객체는 [Buildcontext]와 관련 있습니다. 이 관계는 영구적입니다. State 객체는 절대 [BuildContext]를 바꾸지 않습니다. 하지만 [BuildContext] 자체는 트리 안에서 서브트리의 형태로 이동될 수 있습니다. 이 시점에 [State] 객체는 [mounted]된걸로 간주됩니다.
3. 프레임워크는 [initState]를 호출합니다. [State]의 서브클래스들은 [BuildContext]나 위젯과 관련된 일회성 초기화를 수행하기 위해 [initState]메소드를 오버라이드 합니다.
4. 프레임워크는 [didChangeDependencies]를 호출합니다. [State]의 서브클래스들은 관련된 [InheritedWidget]들의 초기화를 수행하기 위해 [didChangeDependencies] 메소드를 오버라이드 합니다. 만약 [BuildContext.dependOnInheritedWidgetOfExactType] 메소드가 호출되면, 상속받은 위젯이 바뀌거나 트리 안에서 이동하는 경우 [didChangeDependencies] 메소드를 다시 호출합니다.
5. 이 시점에서 [State] 객체는 완전히 초기화된 겁니다. 프레임워크는 UI를 다시 그리기 위해 [build] 메소드를 몇 번이고 다시 호출합니다. [State] 객체는 UI에 영향을 미치는 state데이터가 바뀐 경우 자발적으로 [setState] 메소드를 호출해서 서브트리의 리빌드를 요청합니다.
6. 이때 부모 위젯은 리빌드를 합니다. 그리고 트리의 같은 위치에 같은 [runtimeType]과 같은 위젯 key를 가진 새로운 위젯으로 업데이트를 요청합니다. 이게 일어나면 프레임워크는 [widget] 프로퍼티에 새로운 위젯을 참조하도록 업데이트를 요청합니다. 그리고 이전 위젯을 매개변수로 [didUpdateWidget] 메소드를 호출합니다. [State] 객체는 애니메이션 등과 같은 관련된 위젯의 변화에 대응하기 위해 [didUpdateWidget] 메소드를 오버라이드 합니다. 프레임워크는 [didUpdateWidget] 호출 이후엔 항상 [build] 메소드를 호출합니다. 그 말은 [didUpdateWidget] = [setState] 임을 의미합니다.
7. 개발하는 동안, 핫리로드가 발생하면 [reassemble] 메소드가 호출됩니다. 이건 [initState]메소드를 준비하기 위해 데이터의 재초기화 기능을 제공합니다.
8. [State] 객체가 포함된 서브트리가 트리 안에서 지워지면, 프레임워크는 [deactivate] 메소드를 호출합니다. 서브클래스들은 이 객체와 연결된 트리 안에 있는 다른 엘리먼트들의 연결을 해제하기 위해 이 메소드를 오버라이드 합니다.
9. 이때 프레임워크는 서브트리를 다른 트리에 재삽입합니다. 이게 일어나면, 프레임워크는 [State] 객체에게 트리 안의 새로운 위치로 연결될 기회를 주기 위해 [build] 메소드 호출을 보증합니다. 프레임워크가 서브트리를 재삽입하는 경우, 애니메이션의 마지막 프레임이 끝나기 전에 삭제된 서브트리가 트리에 재삽입됩니다. 이러한 이유로 [State] 객체는 프레임워크가 [dispose] 메서드를 호출할 때까지 대부분의 리소스 해제를 연기할 수 있습니다.
10. 프레임워크가 이 서브트리를 마지막 애니메이션 프레임에 재삽입하지 않는 경우, 프레임워크는 이 [State]가 다시는 build되지 않는다는 의미로 [dispose] 메소드를 호출합니다. 서브클래스들은 이 객체를 참조하는 다른 리소스들도 함께 해제하기 위해 이 메소드를 오버라이드 합니다.
11. 프레임이 [dispose] 메소드를 호출하고 나면, [State] 객체는 unmounted로 고려되고, [mounted] 프로퍼티는 false를 반환하게 됩니다. 이때 [setState]를 호출하면 에러가 납니다. 이 단계가 생명주기의 마지막입니다. dispose된 [State] 오브젝트를 다시 마운트 시킬 방법은 없습니다.
그동안 개발하면서 State의 생명주기에 대해서 깊게 생각해본 적이 없었는데, 정보 변경의 유형별로 dirty로 마크하는 처리가 둘로 나뉘어진다는 점을 알게됐습니다. 이런 메커니즘을 이해하고 있으면 디버깅을 할 때 어느 시점에서 이슈가 생겼는지 파악하기 좋으니 시간이 나면 프레임워크를 깊게 공부하는 것도 좋은 거 같네요.
다음 시간에는 RenderObjectWidget에 대해 알아보겠습니다. 아래 링크를 눌러주세요!
https://mingzan.tistory.com/268
'비전공자를 위한 Flutter > Flutter 심화과정' 카테고리의 다른 글
Flutter How to use Key When, Where, Which key? 언제 어디에 무슨 키를 쓸까? (0) | 2022.05.09 |
---|---|
Flutter How to optimize? 속도 10배 상승시킨 앱 최적화 하는 법 공유 (0) | 2022.05.08 |
Flutter InheritedWidget 란? 코드 뜯어보기 (주석 한글 번역) (0) | 2022.05.05 |
Flutter Tree 트리 구조 및 화면 렌더링 과정 알아보기 (0) | 2022.05.04 |
Flutter StatefulWidget 란? 코드 뜯어보기 (한글 주석 번역) (0) | 2022.05.02 |
댓글