본문 바로가기
비전공자를 위한 Flutter/Flutter 심화과정

Flutter State 생명주기? 코드 뜯어보기

by 밍잔 2022. 5. 7.

지금까지 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

댓글