코딩/개념

앱의 생명주기

흑백 개발자 2025. 4. 12. 16:50
728x90

생명주기는 객체와 위젯의 싸이클이고,

이를 사용하는 이유는 메모리 관리와,초기화로 수행할 시점 알기, 불필요한 작업 중지,가 있다는거다

+상태보존 이유도 있다. 앱에서 폼작성하다 전화와서 받고 백그라운드에 있다가 다시 오면

폼 작성한거 다 없어질 수 있다. 그런 상태를 보존하기 위해 앱의 생명주기에서 메서드 저장하고 호출하도록 한다는데 정확히는 모르겠다.

https://ayoung0073.tistory.com/entry/flutter-app-lifecycle

 

 

 

ㅡㅡㅡㅡㅡ

 

생명주기(lifecycle)는 객체와 위젯의 탄생부터 소멸까지의 전체 사이클을 의미하며, 주요 사용 이유는 다음과 같습니다:

  1. 메모리/자원 관리:
    • 더 이상 필요하지 않은 자원(메모리, 파일 핸들, 데이터베이스 연결 등)을 적절히 해제
    • 메모리 누수 방지와 시스템 자원의 효율적인 사용
  2. 초기화 시점 관리:
    • 객체나 위젯이 생성될 때 필요한 설정이나 데이터 로드를 정확한 시점에 수행
    • 의존성이 준비된 후에 초기화 작업을 진행할 수 있음
  3. 불필요한 작업 중지:
    • 화면에 보이지 않거나 활성화되지 않은 요소의 작업을 일시 중지
    • 백그라운드로 이동했을 때 애니메이션이나 주기적인 업데이트 중지
    • 성능 최적화와 배터리 사용량 감소
  4. 상태 관리와 보존:
    • 앱의 상태 변화(예: 포그라운드/백그라운드 전환)에 따른 적절한 대응
    • 위젯이 재구성될 때 중요한 상태 정보를 보존

생명주기를 이해하고 적절히 활용하면 앱의 안정성, 성능, 사용자 경험을 크게 향상시킬 수 있습니다. 특히 Flutter와 같은 UI 프레임워크에서는 화면 전환이나 위젯 재구성이 빈번하게 일어나기 때문에, 생명주기 관리가 매우 중요합니다.

 

ㅡㅡㅡㅡㅡㅡㅡ

 

Flutter에서의 생명주기 개념과 비교

생명주기(Lifecycle)는 위젯이나 객체가 생성되고, 사용되고, 소멸되는 과정을 의미합니다. 마치 사람이 태어나서 살다가 죽는 것처럼, 앱의 구성 요소들도 비슷한 과정을 거칩니다.

생명주기의 기본 개념

생명주기는 쉽게 말해 "태어나서 죽을 때까지의 여정"입니다. 앱 개발에서는 위젯이나 객체가:

  • 언제 생성되는지
  • 언제 화면에 보이는지
  • 언제 업데이트되는지
  • 언제 사라지는지 를 관리하는 개념입니다.

StatelessWidget의 생명주기

StatelessWidget은 매우 단순한 생명주기를 가집니다:

  1. 생성 - 위젯 인스턴스 생성
  2. build - UI 구성 (build 메서드 호출)
  3. 소멸 - 위젯이 더 이상 필요 없을 때 제거
dart
 
class SimpleText extends StatelessWidget {
  final String text;
  
  SimpleText(this.text) {
    print('StatelessWidget 생성됨'); // 생성 단계
  }
  
  @override
  Widget build(BuildContext context) {
    print('StatelessWidget build 호출됨'); // build 단계
    return Text(text);
  }
  // 소멸 단계는 명시적인 메서드 없음
}

StatelessWidget은 상태가 없어서 단순하지만, 재구성될 때마다 새로 생성되고 build 메서드가 호출됩니다.

StatefulWidget의 생명주기

StatefulWidget은 더 복잡한 생명주기를 가지며, 실제 생명주기 메서드들은 State 클래스에 있습니다:

  1. 생성 단계
    • StatefulWidget 생성자 호출
    • createState() 호출
    • State 객체 생성
    • initState() 호출 - 초기화 작업 수행
  2. 활성 단계
    • didChangeDependencies() - 의존성 변경 시 호출
    • build() - UI 구성
    • didUpdateWidget() - 위젯 재구성 시 호출
    • setState() - 상태 변경 시 build 재호출 트리거
  3. 비활성/소멸 단계
    • deactivate() - 위젯 트리에서 일시적으로 제거될 때
    • dispose() - 위젯이 완전히 제거될 때 (자원 해제)
dart
 
class CounterWidget extends StatefulWidget {
  CounterWidget() {
    print('StatefulWidget 생성자 호출');
  }
  
  @override
  _CounterWidgetState createState() {
    print('createState 호출');
    return _CounterWidgetState();
  }
}

class _CounterWidgetState extends State<CounterWidget> {
  int counter = 0;
  
  @override
  void initState() {
    super.initState();
    print('initState 호출 - 초기화 작업');
    // 데이터 초기화, 이벤트 리스너 등록 등
  }
  
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('didChangeDependencies 호출 - 의존성 변경');
    // InheritedWidget 변경 시 호출됨
  }
  
  @override
  Widget build(BuildContext context) {
    print('build 호출 - UI 구성');
    return Text('Counter: $counter');
  }
  
  @override
  void didUpdateWidget(CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('didUpdateWidget 호출 - 위젯 업데이트');
    // 부모 위젯이 재빌드되어 이 위젯도 재구성될 때
  }
  
  @override
  void deactivate() {
    print('deactivate 호출 - 위젯 트리에서 일시 제거');
    super.deactivate();
    // 위젯이 트리에서 일시적으로 제거될 때 (페이지 이동 등)
  }
  
  @override
  void dispose() {
    print('dispose 호출 - 자원 해제');
    // 타이머 취소, 컨트롤러 해제 등
    super.dispose();
  }
}

Provider의 생명주기

Provider는 자체적인 생명주기가 있으며, 주로 ChangeNotifier를 확장한 클래스를 사용합니다:

  1. 생성 단계
    • Provider 생성 및 초기화
    • ChangeNotifier 클래스 인스턴스 생성
  2. 활성 단계
    • notifyListeners() 호출로 UI 업데이트
    • 데이터 및 상태 변경 관리
  3. 소멸 단계
    • dispose() - Provider가 더 이상 필요 없을 때 자원 해제
dart
 
class CounterProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  
  CounterProvider() {
    print('CounterProvider 생성됨');
  }
  
  void increment() {
    _count++;
    print('상태 변경: $_count');
    notifyListeners(); // 구독자들에게 변경 알림
  }
  
  @override
  void dispose() {
    print('CounterProvider dispose 호출 - 자원 해제');
    super.dispose();
  }
}

// 사용 방법
ChangeNotifierProvider(
  create: (context) => CounterProvider(),
  child: MyApp(),
)

Provider는 일반적으로 StatefulWidget과 비슷한 생명주기를 가지지만, UI와 분리되어 있고 여러 위젯에서 공유될 수 있습니다.

BLoC의 생명주기

BLoC은 보통 Bloc 또는 Cubit 클래스를 확장하여 구현하며, 다음과 같은 생명주기를 가집니다:

  1. 생성 단계
    • BLoC 인스턴스 생성
    • 초기 상태 설정
    • 이벤트 핸들러 등록 (Bloc의 경우)
  2. 활성 단계
    • 이벤트 수신 및 처리 (Bloc)
    • 상태 변경 (emit 호출)
    • 스트림을 통한 상태 전파
  3. 소멸 단계
    • close() - BLoC이 더 이상 필요 없을 때 자원 해제
dart
 
// Bloc 예시
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    print('CounterBloc 생성됨');
    
    on<IncrementEvent>((event, emit) {
      print('이벤트 처리: IncrementEvent');
      emit(state + 1);
    });
    
    on<DecrementEvent>((event, emit) {
      print('이벤트 처리: DecrementEvent');
      emit(state - 1);
    });
  }
  
  @override
  Future<void> close() async {
    print('CounterBloc close 호출 - 자원 해제');
    await super.close();
  }
}

// 사용 방법
BlocProvider(
  create: (context) => CounterBloc(),
  child: MyApp(),
)

BLoC의 생명주기는 스트림 기반이라는 점이 특징입니다. 이벤트 스트림과 상태 스트림이 생성되고 관리되며, close() 메서드에서 이러한 스트림들이 정리됩니다.

생명주기 비교

상태 초기화

  • StatefulWidget: initState()에서 수행
  • Provider: 생성자 또는 별도 init 메서드에서 수행
  • BLoC: 생성자에서 초기 상태 설정 및 이벤트 핸들러 등록

상태 업데이트

  • StatefulWidget: setState()를 호출하여 UI 갱신
  • Provider: notifyListeners()를 호출하여 구독자에게 알림
  • BLoC: emit()을 호출하여 새 상태를 스트림에 전달

자원 해제

  • StatefulWidget: dispose()에서 수행
  • Provider: dispose()에서 수행
  • BLoC: close()에서 수행

위젯 트리 연결

  • StatefulWidget: 직접 위젯 트리에 포함됨
  • Provider: ChangeNotifierProvider를 통해 위젯 트리에 연결
  • BLoC: BlocProvider를 통해 위젯 트리에 연결

각 접근법의 장단점

StatefulWidget

  • 장점: 간단하고 직관적, Flutter의 기본 개념
  • 단점: 상태와 UI가 결합되어 코드 재사용이 어려움

Provider

  • 장점: 상태와 UI 분리, 간단한 설정
  • 단점: 복잡한 상태 변화 추적이 어려울 수 있음

BLoC

  • 장점: 명확한 상태 흐름, 상태 변화 추적 용이, 테스트 용이성
  • 단점: 초기 설정이 복잡하고 보일러플레이트 코드가 많음

생명주기를 잘 이해하고 관리하면 앱의 성능을 최적화하고 메모리 누수와 같은 문제를 방지할 수 있습니다. 각 접근법의 생명주기 특성을 고려하여 프로젝트에 가장 적합한 방식을 선택하는 것이 중요합니다.

 
재시도
K

그럼 생명주기는 위젯이 생성되고 죽는거야? 객체도 생성되고 죽는거고? 왜 그런게 있는거야?

편집

네, 정확합니다. 생명주기(lifecycle)는 위젯이나 객체가 생성되고, 변화하고, 결국 소멸되는 과정을 말합니다.

생명주기가 필요한 이유

생명주기 개념이 있는 이유는 몇 가지 중요한 목적이 있습니다:

  1. 자원 관리
    • 메모리, 네트워크 연결, 파일 핸들과 같은 자원은 제한되어 있습니다
    • 객체가 더 이상 필요 없을 때 이러한 자원을 적절히 정리해야 합니다
    • 예: 카메라 사용 후 종료, 타이머 취소, 데이터베이스 연결 닫기
  2. 적절한 초기화
    • 객체가 생성될 때 필요한 설정을 수행할 시점을 알아야 합니다
    • 예: API 키 로드, 초기 데이터 가져오기, 이벤트 리스너 설정
  3. 성능 최적화
    • 위젯이 화면에 보이지 않을 때 불필요한 작업을 중지할 수 있습니다
    • 예: 화면 밖으로 스크롤된 목록 항목의 애니메이션 중지
  4. 상태 관리
    • 위젯이나 객체의 상태를 적절한 시점에 저장하거나 복원해야 합니다
    • 예: 앱이 백그라운드로 갈 때 작업 중인 데이터 저장

실생활 비유로 생각해보면:

  • 집을 방문하는 손님: 손님이 도착할 때(생성) 환영하고 음식을 준비하고, 손님이 떠날 때(소멸) 뒷정리를 합니다.
  • 식당 테이블: 손님이 앉으면(초기화) 물과 메뉴를 제공하고, 식사 중에는 서비스를 제공하며(활성 상태), 손님이 떠나면(소멸) 테이블을 정리합니다.

Flutter에서 생명주기 관리는 특히 중요합니다. 예를 들어:

dart
 
class VideoPlayerScreen extends StatefulWidget {
  @override
  _VideoPlayerScreenState createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  late VideoPlayerController _controller;

  @override
  void initState() {
    super.initState();
    // 생성 시 비디오 컨트롤러 초기화
    _controller = VideoPlayerController.network('https://example.com/video.mp4')
      ..initialize().then((_) {
        setState(() {});
      });
  }

  @override
  Widget build(BuildContext context) {
    return VideoPlayer(_controller);
  }

  @override
  void dispose() {
    // 위젯 소멸 시 비디오 컨트롤러 정리
    _controller.dispose();
    super.dispose();
  }
}

이 예시에서 initState()에서 비디오 컨트롤러를 초기화하고, dispose()에서 정리합니다. 만약 dispose()에서 컨트롤러를 정리하지 않으면, 화면에서 이 위젯이 사라져도 비디오가 계속 메모리에 남아 결국 메모리 누수로 이어질 수 있습니다.

생명주기는 앱의 자원을 효율적으로 관리하고, 안정성을 높이며, 사용자 경험을 향상시키는 데 필수적인 개념입니다.

 

ㅡㅡㅡㅡㅡㅡㅡㅡ

 

4,번 상태보존 관해 구체적인 예시:

상태 보존이 안 된 경우:

  1. 사용자가 긴 폼을 작성 중
  2. 전화가 와서 앱이 백그라운드로 전환
  3. 전화 통화 후 앱으로 돌아왔더니 모든 입력 내용이 사라짐 (사용자 좌절)

상태 보존이 된 경우:

  1. 사용자가 긴 폼을 작성 중
  2. 전화가 와서 앱이 백그라운드로 전환
  3. 생명주기 메서드에서 작성 중인 데이터를 저장
  4. 전화 통화 후 앱으로 돌아왔을 때 저장된 데이터로 폼을 복원 (좋은 사용자 경험)
728x90