2025. 5. 27. 12:19ㆍDart & Flutter
Flutter를 포함한 대부분의 프레임워크는 비동기(Asynchronous) 프로그래밍을 기반으로 동작합니다. 특히 사용자 경험이 중요한 UI 앱에서는 ‘비동기 처리’가 필수예요.
오늘은 비동기 프로그래밍의 기초이자 핵심인 다음 세 가지 개념을 정리해보겠습니다.
1. Observer 패턴
2. Pub/Sub 패턴
3. Event Loop & Event Queue
📌 1. Observer 패턴이란?
Observer 패턴은 어떤 객체의 상태가 바뀌면, 그것을 “지켜보는 다른 객체”들이 자동으로 반응하는 구조입니다. 쉽게 말해, 값이 바뀌면 그걸 ‘관찰자’가 감지하고 자동으로 업데이트해주는 패턴이죠.
이런 패턴이 왜 필요할까?
예를 들어 숫자를 화면에 보여주는 앱에서 버튼을 누르면 숫자가 1씩 증가하는 구조라고 해볼게요.
매번 값을 바꾼 뒤 직접 화면을 다시 그리면 너무 번거롭고, 실수도 생기기 쉬워요.
그래서 Flutter에서는 상태를 지켜보다가 자동으로 UI를 업데이트해주는 기능을 제공합니다.
대표적인 예가 ValueNotifier + ValueListenableBuilder 조합입니다.
✅ 예제 코드
import 'package: flutter / material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterScreen(),
);
}
}
class CounterScreen extends StatelessWidget {
// 상태를 저장하고, 값이 바뀌면 자동으로 알림을 보냄
final ValueNotifier<int> counter = ValueNotifier<int>(0);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(‘Observer 패턴 예제’)),
body: Center(
// counter를 “지켜보는” 위젯
child: ValueListenableBuilder(
valueListenable: counter,
builder: (context, value, child) {
return Text(‘숫자: $value’, style: TextStyle(fontSize: 24));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 값을 1 증가시키면 화면도 자동으로 바뀜
counter.value += 1;
},
child: Icon(Icons.add),
),
);
}
}
설명:
- counter라는 변수는 ValueNotifier(0)으로 선언되어 있어. 이건 “숫자 상태를 들고 있고, 바뀌면 알릴게요”라는 뜻이에요.
- ValueListenableBuilder는 이 counter를 “관찰”하고 있어. 즉, counter.value가 바뀌면 builder 안의 내용을 자동으로 다시 그려줍니다.
- FloatingActionButton을 누르면 counter.value가 1 증가하고, 그 순간 자동으로 화면이 업데이트돼요.
여기서 “Observer”는 누구일까요?
- counter는 Subject입니다. (즉, 감시당하는 대상)
- ValueListenableBuilder는 Observer입니다. counter를 지켜보고 있다가 값이 바뀌면 반응하는 것이죠.
이 구조가 바로 Observer 패턴입니다.
마무리
- 어떤 값(counter)을 지켜보는(Observer) 구조
- 값이 바뀌면 자동으로 반응하고 UI도 업데이트됨
- Flutter에서는 ValueNotifier + ValueListenableBuilder 조합으로 쉽게 구현 가능
📌 2. Pub/Sub 패턴이란?
Pub/Sub 패턴은 이름 그대로 Publish(발행)와 Subscribe(구독)이라는 개념을 중심으로 동작하는 구조입니다. 메시지를 보내는 쪽과 받는 쪽이 서로 몰라도, 메시지를 주고받을 수 있게 해주는 구조예요.
쉽게 말해서, “누가 메시지를 보내는지”와 “누가 받는지”가 서로를 몰라도 된다는 게 핵심이에요.
이런 패턴이 왜 필요할까?
앱의 여러 화면이나 컴포넌트가 동일한 이벤트를 알아야 할 때, 일일이 연결하는 건 매우 비효율적이에요.
Pub/Sub 패턴은 이벤트만 발행하면, 구독자들이 알아서 반응합니다.
예를 들어 여러분이 퀴즈 풀기 앱을 만들고 있다고 가정해볼게요. 누군가 “퀴즈 다 풀었어요!”라는 이벤트를 앱 전체에 알려주고 싶어요.
그런데 이벤트를 듣는 쪽이 여러 군데에 흩어져 있고, 누가 트리를 꾸몄는지, 어떤 화면에서 발생했는지 등은 상관없어요.
이럴 때 Pub/Sub 구조를 사용하면, “퀴즈를 풀었어요!“라는 메시지만 발행하면 되고, 그걸 구독하고 있는 곳에서는 알아서 반응하면 됩니다.
✅ 구조
- 발행자(Publisher): 이벤트를 발행하는 주체
- 구독자(Subscriber): 이벤트를 구독하고 반응하는 주체
- 중개자(Broker): 중간에서 전달을 돕는 도구 (예: event_bus)
✅ Flutter 예제 – event_bus 패키지 사용
Flutter에서 간단한 Pub/Sub 구조를 구현할 수 있도록 도와주는 패키지가 있어요. 바로 event_bus입니다.
1. pubspec.yaml에 의존성 추가:
dependencies:
flutter:
sdk: flutter
event_bus: ^2.0.0
2. 코드 예제
import 'package:flutter/material.dart';
import 'package:event_bus/event_bus.dart';
// 이벤트 버스 전역 선언
final EventBus eventBus = EventBus();
// 발행할 이벤트 클래스 정의
class QuizCompletedEvent {
final String message;
QuizCompletedEvent(this.message);
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: HomeScreen());
}
}
class HomeScreen extends StatefulWidget {
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String logMessage = '아직 퀴즈를 풀지 않았어요.';
@override
void initState() {
super.initState();
// 이벤트 구독: 누군가 퀴즈를 풀면 반응
eventBus.on<QuizCompletedEvent>().listen((event) {
setState(() {
logMessage = '이벤트 수신: ${event.message}';
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Pub/Sub 패턴 예제')),
body: Center(child: Text(logMessage, style: TextStyle(fontSize: 20))),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 퀴즈 완료 이벤트 발행
eventBus.fire(QuizCompletedEvent("퀴즈를 모두 풀었어요!"));
},
child: Icon(Icons.check),
),
);
}
}
코드 설명:
- TreeDecoratedEvent: 우리가 발행할 이벤트 타입을 클래스로 정의했어요.
- eventBus.on().listen(…): 이 구문은 해당 이벤트가 발행될 때마다 호출될 콜백을 등록하는 구독자입니다.
- eventBus.fire(…): 이벤트를 발행하는 코드입니다. 누가 구독하고 있든, 전파됩니다.
- 발행자와 구독자는 서로를 몰라요! → 느슨한 결합(Decoupling)
Observer 패턴과 비교
| 항목 | Observer 패턴 | Pub/Sub 패턴 |
| 관계 구조 | 주체-관찰자 (직접 연결됨) | 발행자-브로커-구독자 (중개자 있음) |
| 결합도 | 비교적 강한 결합 (주체가 관찰자를 알아야 함) | 느슨한 결합 (서로 몰라도 메시지 전달 가능) |
| Flutter 예시 | ValueNotifier + ValueListenableBuilder | event_bus, Bloc, Firebase Messaging 등 |
| 사용 용도 | UI 상태 변경, 간단한 데이터 흐름 | 이벤트 알림, 앱 전역 상태 전달 등 |
마무리
Pub/Sub 패턴은 이벤트 중심의 앱 구조에서 특히 유용해요. 사용자가 어떤 행동을 했을 때, 그걸 다양한 화면이나 기능들이 알아야 할 경우
하지만 직접 연결시키고 싶지는 않을 때 사용하면 좋아요.
Flutter에서는 event_bus 패키지를 통해 쉽게 구현할 수 있고, 더 나아가 Bloc, Provider, Firebase Cloud Messaging 같은 곳에서도 이 패턴이 광범위하게 사용되고 있어요.
Observer와 Pub/Sub는 비슷해 보이지만 구조와 목적이 다르니, 둘 다 직접 사용해보면서 차이를 익혀보는 걸 추천해요!
📌 3. Event Loop & Event Queue
프로그래밍을 하다 보면 “버튼을 누르면 실행돼요”, “데이터가 로딩되면 화면을 바꿔줘요” 같은 식의 “나중에 실행되는 일”들을 많이 보게 됩니다. 이걸 비동기(asynchronous) 프로그래밍이라고 해요.
비동기 프로그래밍의 핵심 구조는 Event Loop와 Event Queue입니다.
Flutter처럼 싱글 스레드 환경에서 다양한 작업을 동시에 처리하려면, 이 구조를 이해하는 게 정말 중요해요!
이런 패턴이 왜 필요할까?
컴퓨터는 한 번에 한 가지 작업밖에 못 해요 (싱글 스레드). 그런데 우리가 앱을 쓰면서 버튼도 누르고, 서버 응답도 기다리고, 애니메이션도 돌리고, 여러 가지를 동시에 처리하길 원하죠.
이럴 때는 “이건 나중에 해줘~”라고 예약만 해두고, 지금 할 수 있는 일부터 처리하는 방식이 필요해요.
이 구조를 담당하는 게 바로 Event Loop과 Event Queue입니다.
✅ 기본 개념
- Event Loop: “뭐 할 일 생긴 거 있나?” 하고 계속 살펴보는 루프
- Event Queue: “나중에 실행할 작업”들이 줄 서 있는 공간
- Microtask Queue: Event Queue보다 우선순위가 높은 작업 큐
✅ 동작 순서
- 코드가 실행된다.
- 미래에 실행할 작업은 Event Queue에 넣어둔다. (예: 서버 응답 기다리기)
- Event Loop가 메인 코드 실행이 끝나면 Event Queue를 체크한다.
- Queue에 있던 작업을 하나씩 꺼내서 실행한다.
간단한 Dart 코드로 구조를 보자:
✅ 예제 1: Future
void main() {
print('1. 시작');
Future(() {
print('2. Future 실행');
});
print('3. 끝');
}
출력 결과:
1. 시작
3. 끝
2. Future 실행
왜 이런 순서일까요?
- Future(() {})는 나중에 실행할 작업을 Event Queue에 넣은 것입니다.
- main 함수의 나머지 코드가 먼저 실행되고, Event Loop가 Event Queue를 확인한 뒤 Future를 실행해준 거예요.
✅ 예제 2: Microtask vs Future
import 'dart:async';
void main() {
print('1. 시작');
scheduleMicrotask(() {
print('2. 마이크로태스크');
});
Future(() {
print('3. 퓨처');
});
print('4. 끝');
}
출력 결과:
1. 시작
4. 끝
2. 마이크로태스크
3. 퓨처
- 동기 코드 실행 → 1, 4
- Microtask Queue → 2
- Event Queue → 3
✅ Flutter에서의 활용 예시
Flutter는 비동기 UI 프레임워크이기 때문에, Event Loop 구조 위에서 동작해요.
예를 들어:
- Future.delayed: 일정 시간 뒤에 동작
- setState: UI 업데이트 예약
- 사용자 입력: GestureDetector.onTap도 큐에 등록됨
이 모든 것이 Event Loop 구조 위에서 동작합니다.
정리
| 구분 | 설명 | 예시 |
| Event Loop | 작업이 끝났는지 확인하고, Queue에 있는 일 처리함 | Dart, JS의 비동기 처리 루프 |
| Event Queue | 나중에 처리할 일들이 들어있는 큐 | Future, Timer 등 |
| Microtask Queue | Event Queue보다 우선순위가 높은 큐 | scheduleMicrotask() |
📌 마무리 요약
| 구분 | Observer 패턴 | Pub/Sub 패턴 | Event Loop & Queue |
| 개념 | 주체와 관찰자의 직접 연결 | 발행자와 구독자의 느슨한 연결 | 큐에 넣고 하나씩 비동기로 실행 |
| 결합도 | 강한 결합 | 느슨한 결합 | 독립 실행 흐름 |
| Flutter 예시 | ValueNotifier + ValueListenableBuilder | event_bus, Bloc | Future, scheduleMicrotask |
이 세 가지 개념은 Flutter뿐만 아니라 모든 비동기 프로그래밍에서 매우 중요한 기초입니다.
처음에는 어렵게 느껴질 수 있지만, 예제를 직접 돌려보면서 차이를 체감해보면 분명 탄탄하게 익힐 수 있어요!
'Dart & Flutter' 카테고리의 다른 글
| HTTP, REST API, 상태 코드부터 관계형 데이터베이스와 SQL까지 (0) | 2025.06.03 |
|---|---|
| [Flutter] 병렬 처리와 스레드 이해 (Thread, Isolate, 레이스 컨디션) (0) | 2025.05.28 |
| [Dart & Flutter] 개발자를 위한 필수 개념 정리 (Git, 파일 입출력, TDD 등) (0) | 2025.05.20 |
| [Flutter] 함수형 프로그래밍(Functional Programming) (1) | 2025.05.14 |
| [Flutter] 객체지향 프로그래밍(OOP) (0) | 2025.05.14 |