首页 文章详情

Flutter异步编程-sync*和async*生成器函数

熊喵先生 | 149 2021-04-01 18:19 0 0 0
UniSMS (合一短信)

生成器函数可能比较陌生,在平时开发中使用的不是特别频繁,但是因为它也是Dart异步编程的不可缺少的一部分,所以这里还是展开讲解分析。「生成器函数是一种用于延迟生成值序列的函数」,并且在Dart中生成器函数主要分为两种: 「同步生成器函数和异步生成器函数」。我们知道比如 int 类型变量(同步)或 Future<int> (异步)类型变量都只能产生单一的值,而 Iterable<int> 类型变量(同步)或 Stream<int> 类型变量(异步)具有产生多个值的能力。「其中同步生成器函数是立即按需生成值,并不会像Future,Stream那样等待,而异步生成器函数则是异步生成值,也就是它有足够时间去生成值」


Single value(单一的值)Zero or more value(零个值或者更多的值)
Sync(同步)intIterable
Async(异步)FutureStream

1. 为什么需要生成器函数

其实在平时Flutter或者Dart开发中生成器函数使用并不多,但是遇到使用它的场景,有了生成器函数就会非常简单,因为手动去实现值的生成函数还是比较繁杂的。比如说要实现一个同步生成器,需要自定义一个可迭代的类去继承 IterableBase 并且需要重写 iterator 方法用于返回一个新的Iterator对象。为此还得需要声明自己的迭代器类。此外还得实现成员方法 moveNext 和成员属性 current 以此来判断是否迭代到末尾。这还是写一个同步生成器步骤,整个过程还是比较繁杂的。所以Dart给你提供一个方法可以直接生成一个同步生成器函数,简化整个实现的步骤。但是如果要去手动实现一个异步生成器远比同步生成器更复杂,所以直接提供一个简单异步生成器函数会使得整个开发更加高效,可以把精力更加专注于业务。这里就以同步生成器函数举例:

说到同步生成器函数先来回顾下 Iterable 和 Iterator

//Iterator可以将一系列的值依次迭代
abstract class Iterator<E{
  bool moveNext();//表示是否迭代器有下一个值,迭代器把下一个值加载为当前值,直到下一个值为空返回false

  E get current; //返回当前迭代的值,也就是最近迭代一次的值
}

按照上面介绍步骤一起来手动实现一个 Iterable ,实际上也很简单只是简单地包了个壳

class StringIterable extends Iterable<String{
  final List<String> stringList; //实际上List就是一个Iterable

  StringIterable(this.stringList);

  @override
  Iterator<Stringget iterator =>
      stringList.iterator; //通过将List的iterator,赋值给iterator

}

//这样StringIterable就是一个特定类型String的迭代器,我们就可以使用for-in循环进行迭代
void main() {
  var stringIterable = StringIterable([
    "Dart",
    "Java",
    "Kotlin",
    "Swift"
  ]);
  for (var value in stringIterable) {
    print('$value');
  }
}

//甚至你还可以将StringIterable结合map、where、reduce之类操作符函数之类对迭代器值进行变换
void main() {
  var stringIterable = StringIterable([
    "Dart",
    "Java",
    "Kotlin",
    "Swift"
  ]);
  stringIterable
      .map((language) => language.length)//可以结合map一起使用,实际上本质就是Iterable对象转换,将StringIterable转换成MappedIterable
      .forEach(print);
}

输出结果:可以看到上面其实还不是一个真正严格意义手动实现一个 Iterable , 那么问题来了如何手动实现一个同步生成器函数,注意:「同步生成器函数必须返回一个 Iterable 类型,然后需要使用 sync* 修饰该函数,以此来标记该函数是一个同步生成器函数。」

void main() {
  final numbers = getRange(110);
  for (var value in numbers) {
    print('$value');
  }
}

//用于生成一个同步序列
Iterable<int> getRange(int start, int end) sync* { //sync*告诉Dart这个函数是一个按需生产值的同步生成器函数
  for (int i = start; i <= end; i++) {
    yield i;//yield关键字有点像return,但是它是单次返回值,并不会像return直接结束整个函数
  }
}

输出结果:通过对比发现了生成器函数是真的简单方便,只需要通过 sync* 和 yield 关键字就能实现一个任意类型迭代器,比手动实现一个同步生成器函数更加简单,所以应该知道为什么需要生成器函数。其实异步生成器函数也是类似。

2. 什么是生成器(Generator)

生成器函数是「一种可以很方便延迟生成值的序列的函数」,生成器主要分为两种:**同步生成器函数和异步生成器函数。其中同步生成函数需要使用 sync* 关键字修饰,返回一个 Iterable 对象(表示可以顺序访问值的集合);异步生成器函数需要使用 async* 关键字修饰,返回的是一个 Stream 对象(表示异步数据事件)。**此外同步生成器函数是立即按需生成值,并不会像Future,Stream那样等待,而异步生成器函数则是异步生成值,也就是它有足够时间去生成值。

2.1 同步生成器(「Synchronous」 Generator)

同步生成器函数需要配合 sync* 关键字和 yield 关键字,最终返回的是一个 Iterable 对象,其中yield 关键字用于每次返回单次的值,并且需要注意它的返回并不是结束整个函数。

import 'dart:io';

void main() {
  final numbers = countValue(10);
  for (var value in numbers) {
    print('$value');
  }
}

Iterable<int> countValue(int max) sync* {//sync*告诉Dart这个函数是一个按需生产值的同步生成器函数
  for (int i = 0; i < max; i++) {
    yield i; //yield关键字每次返回单次的值
    sleep(Duration(seconds: 1));
  }
}

输出结果:

2.2 异步生成器(「Asynchronous」 Generator)

异步生成器函数需要配合 async* 关键字和 yield 关键字,最终返回的是一个 Stream 对象,需要注意的是「生成Stream也是一个单一订阅模型的Stream,」 也就是说不能同时存在多个订阅者监听否则会出现异常,如果要实现支持多个监听者通过 asBroadcastStream 转换成一个广播订阅模型的Stream。

import 'dart:io';

void main() {
  Stream<int> stream = countStream(10);
  stream.listen((event) {
    print('event value: $event');
  }).onDone(() {
    print('is done');
  });
}

Stream<int> countStream(int max) async* {
  //async*告诉Dart这个函数是生成异步事件流的异步生成器函数
  for (int i = 0; i < max; i++) {
    yield i;
    sleep(Duration(seconds: 1));
  }
}

输出结果:

2.3 yield关键字

「yield关键字」在生成器函数中是用于依序生成每一个值,它有点类似return语句,但是和return语句不一样的是执行一次yield并不会结束整个函数。相反它每次只提供单个值,并挂起(注意不是阻塞)等待调用者请求下一个值,然后它就会再执行一遍,比如上述例子for循环中,「每一次循环执行都会触发yield执行一次返回每次的单值并且进入下一次循环的等待」

Iterable<int> countValue(int max) sync* {//sync*告诉Dart这个函数是一个按需生产值的同步生成器函数
  for (int i = 0; i < max; i++) {
    yield i; //每执行一次循环就会触发当前次yield生成值,然后进入下一次的等待
    sleep(Duration(seconds: 1));
  }
}

2.4 yield* 关键字

yield关键字是用于循环中生产值后跟着一个具体对象或者值,但是yield后面则是跟着一个函数,一般会跟着递归函数,通过它就能实现类似二次递归函数功能。虽然yield关键字也能实现一个二次递归函数但是比较复杂,但是如果使用yield关键字就会更加简单。

//这是使用yield实现二次递归函数
Iterable naturals(n) sync* { 
  if (n > 0) { 
     yield n; 
     for (int i in naturals(n-1)) { 
       yield i; 
     } 
  }


//这是使用yield*实现的二次递归函数
Iterable naturals(n) sync* { 
  if ( n > 0) { 
    yield n; 
    yield* naturals(n-1); 
 } 

2.5 await for

关于await for在Stream那一篇文章中已经有说到,对于一个同步的迭代器Iterable而言我们可以使用for-in循环来遍历每个元素,而对于一个异步的Stream可以很好地使用await for来遍历每个事件元素。

void main() async {
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (int value) {
    return value + 1;
  });
  await stream.forEach((element) => print('stream value is: $element'));
}

3. 如何使用生成器函数(Generator)

3.1 sync* + yield实现同步生成器

void main() {
  final Iterable<int> sequence = countDownBySync(10);
  print('start');
  sequence.forEach((element) => print(element));
  print('end');
}

Iterable<int> countDownBySync(int numsync* {//sync*
  while (num > 0) {
    yield num--;//yield返回值
  }
}

输出结果:

3.2  async* + yield实现异步生成器

void main() {
  final Stream<int> sequence = countDownByAsync(10);
  print('start');
  sequence.listen((event) => print(event)).onDone(() => print('is done'));
  print('end');
}

Stream<int> countDownByAsync(int numasync* {//async*表示异步返回一个Stream
  while (num > 0) {
    yield num--;
  }
}

输出结果:

3.3 sync* + yield*实现递归同步生成器

void main() {
  final Iterable<int> sequence = countDownBySyncRecursive(10);
  print('start');
  sequence.forEach((element) => print(element));
  print('end');
}

Iterable<int> countDownBySyncRecursive(int numsync* {
  if (num > 0) {
    yield num;
    yield* countDownBySyncRecursive(num - 1);//yield*后跟一个递归函数
  }
}

输出结果:

3.4 async* + yield*实现递归异步生成器

void main() {
  final Stream<int> sequence = countDownByAsync(10);
  print('start');
  sequence.listen((event) => print(event)).onDone(() => print('is done'));
  print('end');
}

Stream<int> countDownByAsyncRecursive(int numasync* {
  if (num > 0) {
    yield num;
    yield* countDownByAsyncRecursive(num - 1);//yield*后跟一个递归函数
  }
}

输出结果:

4. 生成器函数(Generator)使用场景

关于生成器函数使用在开发过程其实并不多,但是也不是说关于生成器函数使用场景就没有了,否则这篇文章就没啥意义了。实际上如果有小伙伴接触Flutter开发中其中有一个「Bloc的状态管理框架」,可以发现它里面大量地使用了异步生成器函数,一起来看下:

class CountBloc extends Bloc<CounterEventint{
  @override
  int get initialState => 0;

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.decrement:
        yield state - 1;
        break;
      case CounterEvent.increment:
        yield state + 1;
        break;
    }
  }
}

其实除了上面所说Bloc状态管理框架中使用到的场景,可能还有一种场景那就是异步二次函数递归场景,比如实现某个动画轨迹计算,实际上都是通过一些二次函数计算模拟出来,所以这时候生成器递归函数是不是就可以派上用场了。虽然生成器函数使用场景不是很频繁,但是需要做到某个特定场景第一时间想到用它可以更加简单的实现就可以了。

5. 总结

到这里有关Flutter异步编程系列就结束了,下一个系列将进入Dart注解+APT生成代码技术专题。尽管生成器函数使用在开发过程其实并不多,但是它也作为Flutter异步中一部分,有一些特定场景如果能想到用它来解决,一定会事半功倍的。

感谢关注,熊喵先生愿和你在技术路上一起成长!

good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter