3 분 소요

Stream 출처: dhruvnakum

이번에는 Flutter의 비동기 프로그래밍의 한 축이자 데이터의 시퀀스를 처리할 수 있는 Stream에 대해서 핵심만 쏙쏙 알아보겠습니다!

여기선 독자분들이 Stream이라는 개념 자체를 이해하는 것이 목표입니다.

다트 공식 문서에 따르면, 다트의 비동기 프로그래밍은 Future와 Stream Class로 구현할 수 있다고 명시되어있습니다.

잠시 Future에 대해 복습해보겠습니다 .

Future란?

미래 어느 시점에서 사용할 수 있는 잠재적인 값 또는 에러를 말합니다.

다음은 간단한 예제입니다.

스트림이란?

Stream image

스트림은 데이터나 이벤트가 들어오는 통로로 비동기적인 데이터의 시퀀스를 제공합니다. 여기서 데이터 시퀀스란 유저가 생성한 이벤트들(화면을 탭하는 등)이나 파일로부터 읽어온 데이터를 포함합니다.

그런데 이러한 이벤트들은 어느 타이밍에 발생할지(사용자가 언제 앱 화면을 탭할지) 알 수 없습니다. 따라서 Stream이란 데이터를 받을 수 있는 통로를 만들어놓고 새 이벤트가 들어올 때마다 이를 처리할 수 있습니다.

Future와 Stream의 공통점은?

  • 둘 다 비동기적으로 작동한다는 점
  • 잠재적인(미래 어느 시점) 값을 가진다는 점

Future와 Stream의 차이는?

  • 스트림은 한 개 이상의 Future들의 조합
  • Future는 오직 한 번만 반응할 수 있지만 Stream은 여러 번의 반응이 가능합니다.

스트림에는 두 가지 종류가 있습니다.

  1. Single Subscription: stream에 최대 1개의 listener를 생성할 수 있어요.
  2. Broadcast: stream에 여러 개의 listener를 생성할 수 있어요.

다양한 예제들로 이해해보기

다음 예제들을 살펴보고 조금씩 수정해서 작동시켜보시길 권장드립니다. 그럼 Stream의 작동 방식을 이해하고 친숙해지실 수 있을거에요!

Stream.periodic() → Stream


Stream.periodic(): stream을 만드는 형식 중 하나로, 특정한 시간 간격으로 이벤트를 반복적으로 출력시키는 stream입니다. take()는 몇 번을 반복시킬지 정하는 method 입니다. take를 설정하지 않으면 무한히 반복됩니다.

Stream.iterable()

  // 일반적인 데이터를 다룰 때
  Stream.fromIterable([1, 2, 3, 4, 5])
      .listen((int x) => print('iterable : ${x}'));


일반적인 데이터를 다룰 때 Stream을 형성할 수 있는 방법 중 하나입니다.

Stream.fromFuture()


stream에서 Future 객체를 받는 Stream을 만드는 형식 중 하나입니다. 여기까진 단일 구독(Single Subscription)으로 스트림을 만드는 대략적인 방법을 봤는데요,

다음은 여러 개의 listener를 만들 수 있는 Broadcast 방법으로 Stream을 살펴보겠습니다.

StreamController<Type> controller = StreamController<Type>();

Line 4 : 컨트롤러가 관리하는 스트림을 조작하는 데 사용할 수 있는 컨트롤러가 생성됩니다.
Line 14 : add 메서드를 통해 스트림 컨트롤러에 값을 추가할 수 있습니다.

Stream stream = controller.stream.;


Line 5 : 이 컨트롤러 스트림은 stream 속성을 통해 접근할 수 있어요. 여러 개의 listener를 만들고 싶으면 stream뒤에 asBroadcastStream();을 붙이시면 됩니다.

stream.listen((value) {
  print('Value from controller: $value');
});

Line 7 : 다음은 스트림으로부터 값을 받아보겠습니다.
보통 이를 스트림을 구독(subscribe) 또는 청취(listen)한다고 합니다. 스트림을 구독하면, 구독 후에 스트림에서 방출된 값을 받을 수 있습니다. 이 때 listen() 함수를 통해 새로운 값을 받아서 무엇을 할지(callback) 설정하여 스트림을 구독할 수 있습니다.

async* 함수로 Stream 만들기

스트림을 만드는 또 다른 일반적인 방법은 async* 함수를 사용하는 것입니다. 이 함수를 통해 만든 Stream의 특징은 비동기적으로 실행되고 새로운 값이 들어올 때마다 값을 반환(return(yield)하지만 함수의 실행이 중지되지 않는 것입니다. 다음 예제를 보면서 같이 이해해봅시다.


한 번 실행해보시면 아시겠지만, 랜덤으로 값을 하나 받게 되면 함수 실행이 중지됩니다. 즉, 다른 임의의 값을 받고 싶으면 이 함수를 호출하고 다시 기다려야합니다.

이 때 함수를 한 번만 호출해서 실행을 중지하지 않고 해당 함수로부터 임의의 값을 얻기 위해서 async*와 yield를 사용합니다.

다음 예제는 1초마다 임의의 값을 내보내는 스트림을 반환하는 함수입니다.

5번으로 출력 횟수를 제한해놨는데, for 문 조건식 안의 i 값의 범위를 조정해보세요 ! 원하는 만큼 출력 횟수를 설정할 수 있습니다.

즉 이전 코드와 다른 점은,

  • Future가 아니라 Stream을 반환한다는 점.
  • async가 아니라 async* 라는 점입니다. async*는 코드가 비동기적으로 실행되지만 값을 “반환”해도 계속 실행될 것임을 Runtime에 알려주는 역할을 합니다.
  • 마지막은 함수의 반환을 return이 아니라 yield로 표기한다는 점입니다. 일반 함수랑 똑같이 yield 다음에 표기된 값을 반환하지만 함수가 종료되지 않고 계속 실행한다는 점이 특징입니다.

여기까지 스트림을 생성하고 다루는 법을 알아봤습니다. 자그마한 피드백이라도 감사하게 받고 있습니다!

혹여나 보시다가 이해가 안되시거나 피드백 해주실 부분이 있다면 댓글 환영합니다 !! 긴 글 봐주셔서 감사합니다 :)

참고문헌

Stream class - dart:async library - Dart API
dart:async library - Dart API
Flutter Stream Basics for Beginners | by Dane Mackier | Flutter Community | Medium
Understanding Streams in Flutter (Dart) | by Nitish Kumar Singh | Flutter Community | Medium

댓글남기기