코딩하는 제리

[Flutter/Dart]비동기 프로그래밍 : Stream 본문

Flutter

[Flutter/Dart]비동기 프로그래밍 : Stream

JerryCho 2021. 1. 14. 18:10

 - 스트림은 데이터나 이벤트가 들어오는 통로

 - 데이터의 추가나 변경이 일어나면 이를 관찰하던 곳에서 처리

main() {
  var stream = Stream.periodic(Duration(seconds: 1), (x) => x).take(10);
  
  // 0에서 9까지 1씩 10초동안 1초 간격으로 출력
//   stream.listen(print);
  
  // 0에서 18까지 2씩 10초동안 1초 간격으로 출력
  stream.listen((int number) => print(number + number));
}

/*
 * 스트림은 항상, 만들기 -> 연결(listen) -> 데이터 처리의 과정을 거친다.
 * 1. Stream.periodic()에서 일정 시간마다 데이터(이벤트)를 만든다.
 * 2. listen()을 한다. 스트림에서 만들어지는 이벤트를 관찰하기 시작한다.
 * 3. listen에서 적은대로 데이터를 다룬다. 여기서는 listen(print)라고 적었기에 출력만 한다.
*/

여러 방법으로 Stream 만들기

main() {
  // 일반적인 데이터를 처리할 때
  Stream.fromIterable([1,2,3,4,5]).listen((int x) => print('iterable : $x'));
  
  // 반복적인 처리
  Stream.periodic(Duration(seconds: 1), (x) => x) // 1초에 한번씩 동작
    .take(5) // 5개 까지만 데이터를 처리
    .listen((x) => print('take : $x'));
  
  // 비동기 처리
  Stream.fromFuture(getData()).listen((x) => print('from Future : $x'));
}

Future<String> getData() async {
  // 5초간 대기
  await Future.delayed(Duration(seconds: 5));
  print("Fetched Data");
  return "5초 기다렸다가 온 데이터입니다.";
}
결과
iterable : 1
iterable : 2
iterable : 3
iterable : 4
iterable : 5
take : 0
take : 1
take : 2
take : 3
take : 4
Fetched Data
from Future : 5초 기다렸다가 온 데이터

처음, 끝의 데이터만 사용하기

맨 앞의 데이터만 쓸 수도 있고, 맨 마지막의 데이터만 쓸 수도 있다.

main() {
  // 가장 앞의 데이터만 가져옴. 결과 : 1
  var stream = Stream.fromIterable([1,2,3,4,5]);
  stream.first.then((value) => print("stream.first: $value"));
  
  // 가장 마지막의 데이터만 가져옴. - 결과 : 5
  stream = Stream.fromIterable([1,2,3,4,5]);
  stream.last.then((value) => print("stream.first: $value"));
  
  // 데이터가 비어있는지 확인. 결과 : false
  stream = Stream.fromIterable([1,2,3,4,5]);
  stream.isEmpty.then((value) => print("stream.isEmpty: $value"));
  
  // 데이터 전체 길이. 결과 : 6
  stream = Stream.fromIterable([10,20,30,40,50,60]);
  stream.length.then((value) => print("stream.length: $value"));
}

스트림은 기본적으로 싱글 서브스크립션(Single Subscription)이다. 한 군데에서만 리슨 할 수 있다.

여러 군데에서 사용하려면 브로드캐스트(Broadcast)로 변경해줘야 한다.


스트림 변경(StreamTransformer)

map을 쓰면 스트림을 어느 정도 변경할 수 있다.

main() {
  // 0.2초 간격으로 3개의 데이터에 +10
  var streamMap = Stream.periodic(Duration(milliseconds: 200), (x) => x)
    .take(3)
    .map((x) => x + 10);
  
  streamMap.listen(print);
}

// 결과
10
11
12

하지만 복잡한 처리는 힘들다.

 

StreamTransformer를 사용하면 스트림 변경에 유리.

import 'dart:async';

main() {
  // sink는 스트림 이벤트를 받아들이는 곳
  var transformer = StreamTransformer.fromHandlers(handleData: (value, sink) {
    sink.add("First: $value");
    sink.add("Second: $value");
  });
  
  var stream = Stream.fromIterable(["Good",1,2,3,4,5]);
  stream.transform(transformer).listen((value) => print("listen : $value"));
}
// 결과
listen : First: Good
listen : Second: Good
listen : First: 1
listen : Second: 1
listen : First: 2
listen : Second: 2
listen : First: 3
listen : Second: 3
listen : First: 4
listen : Second: 4
listen : First: 5
listen : Second: 5

async*, yield

함수를 사용해서 스트림을 만드려면?

// async*은 yeild를 사용한다는 의미
Stream<int> createStream(List<int> numbers) async* {
    for (var number in numbers) {
      yield number; // return과 유사.
    }
}

main() {
  var numStream = createStream([1,3,5,7,9]); // 스트림 생성
  numStream.listen((int number) => print(number)); // 스트림 데이터를 받아서 출력
}

함수로 만든 스트림도 이전처럼 동작. 차이점은 async*와 yield를 써서 만들었다는 점이다.

 

  • async* : 제너레이터를 만든다는 뜻. 제너레이터는 뒤늦게 데이터 연산을 할 때 쓰인다. 미리 연산을 하지 않고,
    미루어 두었다가 요청이 있을때 처리한다.
  • yield : return과 유사. return은 한번 리턴하면 함수가 종료되지만, yield는 열린 채로 있어서 필요할 때 다른 연산을 수행할 수 있다.

Broadcast Stream

기본적으로 만들어지는 스트림은 한 곳에서만 listen할 수 있다.

여러 곳에서 listen을 하려면 브로드캐스트를 사용

import 'dart:async';

main() {
  var sc = StreamController.broadcast();
  var broadcastStream = sc.stream;
  broadcastStream.listen((v) => print('broadcast1 $v'));
  broadcastStream.listen((v) => print('broadcast2 $v'));
  
  sc.add(10);
  sc.add(20);
}
// 결과
broadcast1 10
broadcast2 10
broadcast1 20
broadcast2 20

각각의 데이터를 따로 처리하는 걸 알 수 있다.

같은 데이터를 다르 뷰에서 처리할 때 효과적.


Comments