Coaspe

Flutter - Lifecycle of State objects(State 객체의 생명주기) 본문

Flutter/번역

Flutter - Lifecycle of State objects(State 객체의 생명주기)

Coaspe 2023. 2. 17. 14:04

Flutter에 대해 깊게 가장 정확한 정보를 얻고 싶다면, 사실 구글 만큼 중요한 게, 오른쪽 커서 =>  "Go to Definition" 라고 생각합니다. 구조 파악이 쉽고, 주석으로 유용한 정보가 많이 있습니다.

해석본

더보기

State 객체는 다음과 같은 생명주기를 가집니다:

 

* Framework는 [StatefulWidget.createState]를 호출하며 [State] 객체를 생성합니다.

 

* 새로 생성된 [State] 객체는 BuildContext와 연관이 있습니다.

이 연관성은 영구적입니다: [State] 객체는 절대 그 객체의 [BuildContext]를 바꾸지 않습니다. 그러나, [BuildContext]가 그것의 서브트리와 함께 트리를 움직일 수 있습니다. 그 시점에서, [State]객체는 [mounted] 됐다고 간주됩니다.

 

* Framework는 [initState]를 호출합니다. [State]의 서브 클래스들은 한 번만 하는 초기화를 수행하기위해 [initState]를 반드시 오버라이드 해야합니다. 그 초기화는 [BuildContext]나 위젯에 의존하는데, 그 둘은 각각은 [initState] 메소드가 호출 될 때, [context]나 [widget] 속성으로 사용이 가능합니다.

 

* Framework는 [didChangeDependencies]를 호출합니다. [State]의 서브클래스들은 [InheritedWidget]을 포함하는 초기화를 수행하기 위해 [didChangeDependencies]를 호출해야 합니다. 만약 [BuildContext.dependOnInheritedWidgetOfExactType]이 호출되면, 상속된 위젯들이 그 뒤에 변경되거나, 위젯들이 트리에서 움직일 때 [didChangeDependencies] 메소드가 다시 호출됩니다.

 

* 이 시점에서, [State] 객체는 완전히 초기화 되었고 framework는 해당 서브트리를 위한 유저 인터페이스의 정보를 얻기 위해 몇번이고 [build] 메소드를 호출합니다. [State] 객체는 [setState] 메소드를 호출하므로써 자발적으로 그들의 서브 트리를 리빌드하는 것을 요청할 수 있고, 그것은 해당 서브트리에 있는 유저 인터페이스에 영향을 주는 방식으로 그들의 내부 상태가 변경되었다는 것을 의미합니다. 

 

* 그 동안, 부모 위젯은 리빌드되어 트리 업데이트의 이 위치에 새 위젯을 표시하도록  동일한 [runtimeType]과 [Widget.key]로 요청할 수 있습니다. 이게 발생했을 때, framework는 [widget] 속성을 업데이트하고 새 위젯을 참조합니다. 그리고 [didUpdateWidget] 메소드를 이전의 위젯을 인자로 호출합니다. [State] 객체는 그들과 연관된 위젯들의 변화에 반응하기위해 [didUpdateWidget]을 오버라이드 해야합니다. Framwork는 [didUpdateWidget]을 호출 한 뒤 [build]를 호출해야합니다. 그것은 [didUpdateWidget]에서 [setState]을 호출하는 것은 불필요하다는 것을 의미합니다.

 

* 개발하는 동안, 핫리로드가 발생하면, [reassemble] 메소드가 호출됩니다. 이건 [initState] 메소드 안에 준비되어 있는 데이터가 재초기화 될 수 있게 합니다.

 

* 만약 [State] 객체를 포함하는 서브트리가 트리에서 제거되면, framework는 [deactivate] 메소드를 호출합니다.  서브 클래스들은 이 객체들과 트리안에 다른 요소들 간의 링크들을 정리하기 위해 해당 메소드를 오버라이드 해야합니다.

 

* 이 시점에, framework는 이 서브트리를 다른 트리의 부분으로 재삽입합니다. 만약 그런 일이 발생한다면, framework는 [State] 객체가 트리에서의 그것의 새로운 위치에 조정하기 위한 기회를 제공하기 위해 [build]를 호출하는 것을 보장해야합니다. framework 가 이 하위 트리를 다시 삽입할 경우 하위 트리가 트리에서 제거된 애니메이션 프레임이 종료되기 전에 다시 삽입됩니다. 이러한 이유로, [State] 객체들은 [dispose] 메소드를 호출할 때 까지 대부분의 리소스들을 릴리즈하는 것을 미룰 수 있습니다.

 

* 만약 framework가 현재 애니메이션 프레임의 끝에 해당 서브 트리를 재삽입하지 못했다면, framework는 [dispose]를 호출하고, 그것은 이 [State] 객체는 다시 빌드되지 않을 것이라는 것을 의미합니다. 서브클래스들은 이 객체에 의해 가지고 있는 리소스들을 릴리즈하기 위해 이 메소드를 오버라이드 해야합니다.

 

* framework가 [dispose]를 호출 한 뒤, [State] 객체는 언마운트 되었다고 간주되고 [mounted] 속성은 false가 됩니다. 이 시점에 [setState]를 호출하는 것은 에러를 발생합니다. 이 단계는 생명주기의 마지막 단계입니다: disposed된 [State] 객체를 다시 마운트 시킬 방법은 없습니다.

위의 해석본을 요약한 그림은 다음과 같습니다.


createState()

더보기

createState()는 State 클래스에 존재하지 않고, StatefulWidget 클래스에 존재합니다. 사용 할 때 override하고 보통 다음과 같은 형태입니다.

initState()

더보기

해당 state 객체가 tree에 삽입될 때  정확히 1번 호출됩니다.

 

해당 객체가 삽입된 트리의 특정 위치(i.e., context)나, 이 객체를 configure하기 위해 사용된 widget(i.e., widget)을 사용하여 어떠한 초기화를 수행하고 싶다면, ininState()를 오버라이드 하시면 됩니다.

 

State의 build 메소드가 ChangeNotifierStream처럼 스스로 상태를 변화하는 객체나 notification을 받기 위해 subscribe하는 어떤 객체들과 의존성을 가지고 있다면, initState, didUpdateWidget, dispose에서 subscribe, unsubscribe를 주의깊게 구현해야 합니다.

 

  • initState에서 객체를 subscribe하세요.
  • 업데이트된 위젯 구성이 해당 객체를 대체한다면, didUpdageWidget에서 old 객체를 unsubscribe하고, 새로운 객체를 subscribe하세요. 
  • dispose에서 객체를 unsubscribe하세요.

initState()에서 BuildContext.dependOnInheritedWidgetOfExactType를 실행할 수 없습니다. 그러나, BuildContext.dependOnInheritedWidgetOfExactType를 사용할 수 있는 didChangeDependencies 가 이어서 호출됩니다.

 

그리고 해당 메소드의 시작은 'super.initState()'로 시작해야만 합니다.

 

didUpdateWidget()

더보기

Widget의 구성이 변화될 때 호출되는 함수 입니다.

부모 위젯이 rebuild되고, 같은 runtimeType과 Widget.key로 트리의 해당 위젯의 위치를 업데이트하여 새로운 위젯을 디스플레이하는 경우, framework는 State 객체의 widget 프로퍼티를 업데이트하고 didUpdateWidget 함수에 이전의 위젯을 인자로 넘겨 호출합니다.

 

wiget이 변화할 때 무언가 하고 싶다면, 이 함수를 오버라이드하세요.

 

framework는 didUpdateWidget 호출 후 항상 build를 호출합니다. 그러므로 didUpdateWidget에서 setState를 호출하는 것은 불필요합니다.

 

이 메소드의 시작은 'super.didUpdateWidget(oldWidget)'으로 시작해야합니다.

didChangeDependencies()

더보기

State 객체의 dependency가 변화되었을 때 호출됩니다.

 

예를 들어, 직전의 build 호출이 최근에 변경된 InheritedWidget을 참조하고 있다면, framework는 해당 객체가 변경되었다는 것을 알리기 위해 이 메소드를 호출합니다.

 

이 메소드는 initState 직후에 호출됩니다. 이 메소드에서 BuildContext.dependOnInheritedWidgetOfExactType을 호출하는 것이 안전합니다.

 

Framework가 항상 dependency의 변화 후에 build를 호출하기 때문에, 이 메소드를 오버라이드하는 경우는 드뭅니다. 가끔 서브클래스들이 network fetches 같이 매 build 마다 실행하기 힘든 expensive work를 수행하기 위해 이 메소드를 오버라이드합니다.

deactivate()

더보기

State 객체가 트리에서 제거될 때 호출되는 함수입니다.

 

Framework는 State 객체를 트리에서 삭제할 때마다 해당 메소드를 호출합니다. 때때로, framework는 State 객체를 트리의 다른 곳으로 재삽입 합니다.(e.g., GlobalKey를 사용하여 이 State 객체가 포함된 하위 트리가 트리의 한 위치에서 다른 위치로 이식된(grafted) 경우). 만약 그런일이 벌어지면, framework는 activate 객체를 호출하므로써, 해당 객체가 deactivate에서 릴리즈한 리소스들을 다시 얻을 수 있는 기회를 줍니다. 그리고 트리에서 새로운 위치를 얻을 수 있게 build 메소드를 호출합니다. 만약 framework가 이 서브 트리를 재삽입하면, 해당 서브 트리가 트리에서 제거된 애니메이션 프레임이 끝나기 전에 위의 작업을 수행합니다. 이러한 작동 방식 덕에, State 객체는 framework가 dispose 메소드를 호출하기 전 까지 리소스들을 릴리즈하는 것을 미룰 수 있습니다.

 

서브클래스들은 이 객체와 트리의 다른 elements들 사이의 링크를 없애기 위해 deactivate() 메소드를 오버라이드합니다.

 

이 메소드의 구현은 'super.deactivate()'로 끝나야합니다.

dispose()

더보기

트리에서 객체를 영구적으로 제거할 때 호출합니다.

 

Framework는 State 객체가 다시 build하지 않는다면 이 함수를 호출합니다. dispose를 호출한 다음, 해당 State 객체는 unmount 된 것으로 간주되고, mounted 프로퍼티가 false로 설정됩니다. 이 시점에서 setState를 호출하면 에러를 발생시킵니다. 이 상태는 영구적입니다: dispose된 State 객체는 절대 다시 mount 될 수 없습니다.

 

서브 클래스는 State 객체가 가지고 있는 리소스를 릴리즈 하기 위해 dispose 함수를 오버라이드합니다.

 

 

이 메소드의 구현은 'super.dispose()' 로 끝나야합니다.

1236~40

State의 상태를 체크하는 과정

activate()

더보기

deactivate 로 트리에서 제거된 이후에 재삽입하기 위해 호출하는 함수입니다.

 

해당 메소드의 구현은 'super.activate()'로 시작합니다.


그리고 framework.dart에는 다음과 같은 _StateLifecycle enum이 정의되어 있습니다. 각각

created는 createState() <-> initState()의 사이
initialized는 initState() <-> didChangeDependencies의 사이
ready는 dispose()가 호출되기 전 상태
defunct는 dispose()가 호출된 후의 상태

원본  package:flutter/framework.dart

더보기
/// [State] objects have the following lifecycle:
///
///  * The framework creates a [State] object by calling
///    [StatefulWidget.createState].
///  * The newly created [State] object is associated with a [BuildContext].
///    This association is permanent: the [State] object will never change its
///    [BuildContext]. However, the [BuildContext] itself can be moved around
///    the tree along with its subtree. At this point, the [State] object is
///    considered [mounted].
///  * The framework calls [initState]. Subclasses of [State] should override
///    [initState] to perform one-time initialization that depends on the
///    [BuildContext] or the widget, which are available as the [context] and
///    [widget] properties, respectively, when the [initState] method is
///    called.
///  * The framework calls [didChangeDependencies]. Subclasses of [State] should
///    override [didChangeDependencies] to perform initialization involving
///    [InheritedWidget]s. If [BuildContext.dependOnInheritedWidgetOfExactType] is
///    called, the [didChangeDependencies] method will be called again if the
///    inherited widgets subsequently change or if the widget moves in the tree.
///  * At this point, the [State] object is fully initialized and the framework
///    might call its [build] method any number of times to obtain a
///    description of the user interface for this subtree. [State] objects can
///    spontaneously request to rebuild their subtree by callings their
///    [setState] method, which indicates that some of their internal state
///    has changed in a way that might impact the user interface in this
///    subtree.
///  * During this time, a parent widget might rebuild and request that this
///    location in the tree update to display a new widget with the same
///    [runtimeType] and [Widget.key]. When this happens, the framework will
///    update the [widget] property to refer to the new widget and then call the
///    [didUpdateWidget] method with the previous widget as an argument. [State]
///    objects should override [didUpdateWidget] to respond to changes in their
///    associated widget (e.g., to start implicit animations). The framework
///    always calls [build] after calling [didUpdateWidget], which means any
///    calls to [setState] in [didUpdateWidget] are redundant.
///  * During development, if a hot reload occurs (whether initiated from the
///    command line `flutter` tool by pressing `r`, or from an IDE), the
///    [reassemble] method is called. This provides an opportunity to
///    reinitialize any data that was prepared in the [initState] method.
///  * If the subtree containing the [State] object is removed from the tree
///    (e.g., because the parent built a widget with a different [runtimeType]
///    or [Widget.key]), the framework calls the [deactivate] method. Subclasses
///    should override this method to clean up any links between this object
///    and other elements in the tree (e.g. if you have provided an ancestor
///    with a pointer to a descendant's [RenderObject]).
///  * At this point, the framework might reinsert this subtree into another
///    part of the tree. If that happens, the framework will ensure that it
///    calls [build] to give the [State] object a chance to adapt to its new
///    location in the tree. If the framework does reinsert this subtree, it
///    will do so before the end of the animation frame in which the subtree was
///    removed from the tree. For this reason, [State] objects can defer
///    releasing most resources until the framework calls their [dispose]
///    method.
///  * If the framework does not reinsert this subtree by the end of the current
///    animation frame, the framework will call [dispose], which indicates that
///    this [State] object will never build again. Subclasses should override
///    this method to release any resources retained by this object (e.g.,
///    stop any active animations).
///  * After the framework calls [dispose], the [State] object is considered
///    unmounted and the [mounted] property is false. It is an error to call
///    [setState] at this point. This stage of the lifecycle is terminal: there
///    is no way to remount a [State] object that has been disposed.
Comments