본문 바로가기
삽질하는 개발자 hashblown

프론트엔드 개발자의 TIL #021

by hashblown 2019. 11. 30.

TIL #021

191130 토

 


오늘 배운 점

<Flutter>

1. Generator (생성자 함수)

  • 비동기 데이터 처리를 위한 (거의) 마지막 단계의 공부다. 사실 공부할 내용은 훨씬 많지만 이렇게 간단하게나마 5번에 걸쳐 하나씩 비동기 코딩을 뜯어보는 것도 유익하다고 생각한다!
  • Sync 데이터의 생성자가 Iterable형이라면, Async 데이터의 생성자는 Stream형 반환 type이다. 둘의 가장 큰 차이점은 sync 생성자는 요구에 따라(on demand) 바로바로 값을 산출하기 때문에 Future나 Stream을 기다릴 수 없지만, async은 자기 방식대로 값을 산출할 수 있기 때문에 await 키워드 등을 사용할 수 있다는 것이다.
abstract class Iterator<E> {
  bool moveNext();
  E get current;
}

class MyStrings extends Iterable<String> {
  MyStrings(this.strings);
  final List<String> strings;
  
  Iterator<String> get iterator =>
    strings.iterator;
}

void main() {
  final myStrings = MyStrings([
    'One',
    'Two',
    'Three',
  ]);
  
  for (final str in myStrings) {
    print(str);
  }
  
  //map 활용
  final lengths = myStrings.map((s) => s.length);
  
  for (final length in lengths) {
    print(length);
  }
}
  • Iterator는 일련의 값을 한번에 하나씩, 반복할 수 있게 해주는 간단한 인터페이스다. 현재 값을 도출해주는 current 속성이 있고, 최근 값과 관련된 일이 끝났으니 다음 값을 current로 불러오도록 하는 moveNext 속성이 있다.
  • Iterable은 특정한 유형의 Iterator를 줄 수 있는 class이다. 위 코드의 경우 Iterator<String>을 반환하는 Iterator 속성을 가져야 한다. Iterable의 장점은 각 값을 순환시키기 위해 for ~in 루프에 넣어서 활용할 수 있다는 것이다. where과 map도 함께 사용될 수 있다. 
Iterable<int> getRange(int start, int finish) sync* {
  for (int i = start; i <= finish; i++) {
    yield i;
  }
}

void main() {
  final numbers = getRange(1, 10);
//    .where((num) => num % 2 == 0);
  
  for (int val in numbers) {
    print(val);
  }
  
//  numbers.forEach(print);
}
  • 이런 Iterable을 반환하는 생성자는 어떻게 만들까? 우선 Iterable을 반환하는 함수를 정의한다. 여기서는 시작부터 끝까지 다양한 숫자를 반환하는 함수를 예로 들었다. 다음 sync* 를 사용해서 함수를 동기식 생성자라고 표시해준다. (해당 함수가 요청하면 복수의 값을 생성할 것임을 Dart에게 알리는 방법 중 하나다.) 그 다음 yield 키워드를 사용하여 순서대로 각 값을 산출하면 된다. return과 비슷한 역할을 하지만 함수를 끝내지 않고 값 하나를 제공한 후 다음 값을 요청하기를 기다린다는 점(여기서는 loop을 실행한다)에서 차이가 있다. 이 함수는 Iterable을 반복하지 않는 한 실행되지 않는다. (동기적으로 값을 제공)
  • 이 예시에서는 main 함수를 실행하면 1부터 10까지의 숫자를 출력하게 된다. where을 통해 짝수만 출력하게 할 수도 있고, forEach를 활용하여 실제 loop를 대체할 수도 있다.
Iterable<int> getRange(int start, int finish) sync* {
  if (start <= finish) {
    yield start;
    for (final val in getRange(start + 1, finish)) {
      yield val;
    }
  }
}

Iterable<int> getRange(int start, int finish) sync* {
  if (start <= finish) {
    yield start;
    yield* getRange(start + 1, finish);
  }
}
  • 또한, yield는 다른 Iterable을 산출하는 변수(variant)를 갖는다. 위 예시는 getRange의 호출에 반환된 Iterable을 loop으로 감싸서 값을 한 번에 하나씩 산출할 수 있게 한 것이다. 첫 call에서는 loop이 9번 실행된다. 다음엔 8, 그 다음엔 7..
  • 이 내용을 loop 없이 yield* 로 더 간단하게 해결할 수 있다. 이는 전체 Iterable을 한 번에 한 값씩 산출하게 해준다.

 

Future<int> fetchDouble(int val) {
  //Fetch val * 2 from server
}

Stream<int> fetchDoubles(int start, int finish) async* {
  for (int i = start; i <= finish; i++) {
    yield await fetchDouble(i);
  }
  
  //재귀
  if (start <= finish) {
    yield await fetchDouble(start);
    yield* fetchDoubles(start + 1, finish);
  }
}

void main() {
  fetchDoubles(1, 10).listen(print);
}
  • 이제 비동기적으로 처리하는 방법을 알아보자. 모든 수학적 계산에 네트워크 호출이 필요한 상황을 가정해본다. 여기서 여러 값들 중에서 하나씩 값을 얻어 처리하고 싶은 경우에 비동기식 생성자 함수를 사용하게 된다. 우선 특정 유형의 Stream을 반환하는 함수를 정의한다. 그 다음 async* 키워드를 추가하여 이 함수가 비동기 생성자임을 표시해준다. 그리고 yield를 사용하여 값을 받으면 산출하도록 한다. 여기서 await을 통해 fetchDouble에서의 Future를 기다린다는 점을 확인한다. 
  • yield*를 활용하여 fetchDoubles 함수를 재귀(recursive)적으로 만들어 코드를 단단하고 효율적으로 만들 수 있다. 

내일 배울 점


더보기

- Into the Unknown~

댓글