시간이 필요한 어떤 연산 작업을 실행할 때 굉장히 유용함.
예를 들어 아래 코드에서 이부분
에 http 와 관련된 처리들을 한 후 yield
로 던져지는 값들을 stream 을 통해 연속적으로 처리할 때 굉장히 유용하다는 뜻이다.
async*
혹은 streamController
로 생성Stream<int> countStream(int max) async* {
for (int i=1; iM=max; i++) {
print("before");
// 이부분
yield i;
print("after");
}
}
countStream(10).listen((event) {
print(event);
}
)
// result
"before"
1
"after"
"before"
2
"after"
...
자, 그럼 두가지 방식의 차이점은 무엇이냐? 바로 async*
로 생성하여 stream 처리한 부분이 코드와 동일하게 진행된다는 것이다.
final controller = StreamController<int>();
final stream = controller.stream;
stream.listen((event) {
print(event);
}
);
void addAtatToTheSink(StreamController<int> controller) async {
for(int i=1; i<4; i++) {
print("before");
controller.sink.add(i);
print("after");
await sleepAsync(1.seconds);
}
}
// result
"before"
"after"
1
"before"
"after"
2
...
countStream(4).transform(utf8.decoder).listen(...
countStream(4).map((e)->e+"").listen(...
이때, transform 함수는 사실 많이 사용하진 않을 것이다.
왜냐하면 이 transform 함수는 보통 streamingText 혹은 streamingAudio 를 사용하여 다루기 때문이다.
물론 접근은 다음과 같이 snapshot.data 로 접근하면 되는데 문제는 data 가 nullable 하기 때문에 이를 처리하고 작성해줘야한다.
대표적으로 CircularProgressIndicator() 와 같은 로딩 state 로 처리해주면 좋다.
.asBroadcastStream()
만 stream 객체 뒤에 붙여주면 된다.countStream(4).asBroadcastStream()
cancelOnError: false
를 넣어주면 된다. 근데 이 값이 default 이다.참고로 Stream 생성 함수의 에러처리도 throws 를 사용하면 해당 함수를 곧바로 종료하기 때문에 그렇기 싫다면 yield*
키워드를 사용하면 된다.
countStream(5).listen((event){
print(event);
}, cancelOnError: false).onError((e){
print(e);
})
Stream<int> countStream(int max) async* {
for (int i=1; i<=max; i++) {
if(i==2){
yield* Stream.error(Exception("에러 발생"));
}
yield i;
await sleepAsync(1.seconds);
}
}
iterator 는 마치 커서와 같다.
그래서 다음과 같이 작성할 수 있다.
async*
는 Stream 객체를 만들었다면 sync*
는 iterable 객체를 만드는 예약어이다.
yield*
Iteralbe 함수를 확장시키는 역할을 한다. 예를들어 yield 값 후에도 yield* 가 있다면 해당 값도 함께 반환한다.
이는 Stream 에도 동일하게 적용된다.
yield
는 생산하다 라는 뜻이다. 따라서 iterable, Stream 을 Generator 라고 부른다.
Iterable<String> countIterable(int max) sync* {
for (int i=1; i<= max; i++){
yield i.toString();
}
yield "이터러블";
yield* countIterable(max);
}
while iterator.moveNaxt()){
print(iterator.current);
}
await for (final message in streamData(3)){
// 구현 부
}
flutter 는 보통 10억 번의 연산 횟수가 넘어가면 굉장히 느려지는데 이를 싱글 쓰레드에서 처리하면 하루죙일 걸린다.
따라서 우리는 Isolsates 를 이용하여 이를 더 빠르게 처리할 수 있다.
(그런데 사실 우리가 한 번에 10만 줄이 훨씬 넘는 연산을 하진 않고 보통 동영상을 처리한다든지 굉장히 큰 파일의 압축을 푼다든지 이럴 때 사용된다.)
우선 ReceivePort
는 Stream 객체를 구현한 객체이다.
그리고 .spawn()
이라는 함수에 작업할 무거운 함수를 넣어주고 작동시키면 되는데 이때,
중요한 점은 현재 flutter 쓰레드가 아니고 다른 쓰레드에서 실행되기 때문에 flutter 쓰레드의 어떠한 값도 모두 공유가 안 된다는 점을 인지하면 된다.
만약 공유하고 싶다면 앞서 언급한 port 를 사용하여 값을 던져주면 된다. 그래서 위 코드블럭에서 .spawn()
의 첫번째 매개변수가 sendPort 인 것이다.
그리고 던져주는 것은 다음과 같이 작성해여 보낼 수 있다.
그리고 혹시 중간에 멈추고 싶다면 Isolate 의 kill 함수를 사용하면 된다.
.spawn*()
은 데이터를 넘기기도 복잡하고 ReceivePort 도 사용하고 복잡해서 .run()
함수를 만들었다.
대신 따로 에러 핸들링 파라미터가 없어 try-catch
로 에러를 처리해야한다.
대신 progress indicator 가 없기 때문에 유의하자.
다음 두코드의 차이점을 설명하시오.
const RoundButtonTheme(
this.bgColor,
this.textColor,
this.borderColor, {
this.backgroundColorProvider,
}) : shadowColor = Colors.transparent;
const RoundButtonTheme(
this.bgColor,
this.textColor,
this.borderColor, {
this.shadowColor = Colors.transparent,
this.backgroundColorProvider,
});
코드 스니펫
이 코드는 생성자의 초기화 목록(initializer list)을 사용하여 shadowColor
를 초기화한다.
초기화 목록은 생성자 바디가 실행되기 전에 클래스 속성을 초기화하는 데 사용된다.
이 경우, shadowColor
는 항상 Colors.transparent
로 초기화되는데, 즉, 생성자를 호출할 때 shadowColor
에 대한 다른 값을 전달할 수 없다.
참고로 backgroundColorProvider
는 선택적 명명된 매개변수이다.
코드 스니펫
이 코드는 선택적 명명된 매개변수(shadowColor)에 기본값(Colors.transparent
)을 할당한다.
생성자를 호출할 때 shadowColor
값을 명시적으로 지정하지 않으면 기본값인 Colors.transparent
가 사용된다.
생성자를 호출할 때 shadowColor
에 다른 값을 전달하면 해당 값이 사용된다.
결론
첫 번째 코드는 shadowColor
를 항상 Colors.transparent
로 고정
두 번째 코드는 shadowColor
에 기본값을 제공하지만, 생성자를 호출할 때 다른 값으로 변경