Flutter異步編程-sync*和async*生成器函數(shù)
生成器函數(shù)可能比較陌生,在平時(shí)開發(fā)中使用的不是特別頻繁,但是因?yàn)樗彩荄art異步編程的不可缺少的一部分,所以這里還是展開講解分析。「生成器函數(shù)是一種用于延遲生成值序列的函數(shù)」,并且在Dart中生成器函數(shù)主要分為兩種: 「同步生成器函數(shù)和異步生成器函數(shù)」。我們知道比如 int 類型變量(同步)或 Future<int> (異步)類型變量都只能產(chǎn)生單一的值,而 Iterable<int> 類型變量(同步)或 Stream<int> 類型變量(異步)具有產(chǎn)生多個(gè)值的能力。「其中同步生成器函數(shù)是立即按需生成值,并不會(huì)像Future,Stream那樣等待,而異步生成器函數(shù)則是異步生成值,也就是它有足夠時(shí)間去生成值」。
| Single value(單一的值) | Zero or more value(零個(gè)值或者更多的值) | |
|---|---|---|
| Sync(同步) | int | Iterable |
| Async(異步) | Future | Stream |
1. 為什么需要生成器函數(shù)
其實(shí)在平時(shí)Flutter或者Dart開發(fā)中生成器函數(shù)使用并不多,但是遇到使用它的場(chǎng)景,有了生成器函數(shù)就會(huì)非常簡(jiǎn)單,因?yàn)槭謩?dòng)去實(shí)現(xiàn)值的生成函數(shù)還是比較繁雜的。比如說要實(shí)現(xiàn)一個(gè)同步生成器,需要自定義一個(gè)可迭代的類去繼承 IterableBase 并且需要重寫 iterator 方法用于返回一個(gè)新的Iterator對(duì)象。為此還得需要聲明自己的迭代器類。此外還得實(shí)現(xiàn)成員方法 moveNext 和成員屬性 current 以此來判斷是否迭代到末尾。這還是寫一個(gè)同步生成器步驟,整個(gè)過程還是比較繁雜的。所以Dart給你提供一個(gè)方法可以直接生成一個(gè)同步生成器函數(shù),簡(jiǎn)化整個(gè)實(shí)現(xiàn)的步驟。但是如果要去手動(dòng)實(shí)現(xiàn)一個(gè)異步生成器遠(yuǎn)比同步生成器更復(fù)雜,所以直接提供一個(gè)簡(jiǎn)單異步生成器函數(shù)會(huì)使得整個(gè)開發(fā)更加高效,可以把精力更加專注于業(yè)務(wù)。這里就以同步生成器函數(shù)舉例:
說到同步生成器函數(shù)先來回顧下 Iterable 和 Iterator
//Iterator可以將一系列的值依次迭代
abstract class Iterator<E> {
bool moveNext();//表示是否迭代器有下一個(gè)值,迭代器把下一個(gè)值加載為當(dāng)前值,直到下一個(gè)值為空返回false
E get current; //返回當(dāng)前迭代的值,也就是最近迭代一次的值
}
按照上面介紹步驟一起來手動(dòng)實(shí)現(xiàn)一個(gè) Iterable ,實(shí)際上也很簡(jiǎn)單只是簡(jiǎn)單地包了個(gè)殼
class StringIterable extends Iterable<String> {
final List<String> stringList; //實(shí)際上List就是一個(gè)Iterable
StringIterable(this.stringList);
@override
Iterator<String> get iterator =>
stringList.iterator; //通過將List的iterator,賦值給iterator
}
//這樣StringIterable就是一個(gè)特定類型String的迭代器,我們就可以使用for-in循環(huán)進(jìn)行迭代
void main() {
var stringIterable = StringIterable([
"Dart",
"Java",
"Kotlin",
"Swift"
]);
for (var value in stringIterable) {
print('$value');
}
}
//甚至你還可以將StringIterable結(jié)合map、where、reduce之類操作符函數(shù)之類對(duì)迭代器值進(jìn)行變換
void main() {
var stringIterable = StringIterable([
"Dart",
"Java",
"Kotlin",
"Swift"
]);
stringIterable
.map((language) => language.length)//可以結(jié)合map一起使用,實(shí)際上本質(zhì)就是Iterable對(duì)象轉(zhuǎn)換,將StringIterable轉(zhuǎn)換成MappedIterable
.forEach(print);
}
輸出結(jié)果:
可以看到上面其實(shí)還不是一個(gè)真正嚴(yán)格意義手動(dòng)實(shí)現(xiàn)一個(gè) Iterable , 那么問題來了如何手動(dòng)實(shí)現(xiàn)一個(gè)同步生成器函數(shù),注意:「同步生成器函數(shù)必須返回一個(gè) Iterable 類型,然后需要使用 sync* 修飾該函數(shù),以此來標(biāo)記該函數(shù)是一個(gè)同步生成器函數(shù)。」
void main() {
final numbers = getRange(1, 10);
for (var value in numbers) {
print('$value');
}
}
//用于生成一個(gè)同步序列
Iterable<int> getRange(int start, int end) sync* { //sync*告訴Dart這個(gè)函數(shù)是一個(gè)按需生產(chǎn)值的同步生成器函數(shù)
for (int i = start; i <= end; i++) {
yield i;//yield關(guān)鍵字有點(diǎn)像return,但是它是單次返回值,并不會(huì)像return直接結(jié)束整個(gè)函數(shù)
}
}
輸出結(jié)果:
通過對(duì)比發(fā)現(xiàn)了生成器函數(shù)是真的簡(jiǎn)單方便,只需要通過 sync* 和 yield 關(guān)鍵字就能實(shí)現(xiàn)一個(gè)任意類型迭代器,比手動(dòng)實(shí)現(xiàn)一個(gè)同步生成器函數(shù)更加簡(jiǎn)單,所以應(yīng)該知道為什么需要生成器函數(shù)。其實(shí)異步生成器函數(shù)也是類似。
2. 什么是生成器(Generator)
生成器函數(shù)是「一種可以很方便延遲生成值的序列的函數(shù)」,生成器主要分為兩種:**同步生成器函數(shù)和異步生成器函數(shù)。其中同步生成函數(shù)需要使用 sync* 關(guān)鍵字修飾,返回一個(gè) Iterable 對(duì)象(表示可以順序訪問值的集合);異步生成器函數(shù)需要使用 async* 關(guān)鍵字修飾,返回的是一個(gè) Stream 對(duì)象(表示異步數(shù)據(jù)事件)。**此外同步生成器函數(shù)是立即按需生成值,并不會(huì)像Future,Stream那樣等待,而異步生成器函數(shù)則是異步生成值,也就是它有足夠時(shí)間去生成值。
2.1 同步生成器(「Synchronous」 Generator)
同步生成器函數(shù)需要配合 sync* 關(guān)鍵字和 yield 關(guān)鍵字,最終返回的是一個(gè) Iterable 對(duì)象,其中yield 關(guān)鍵字用于每次返回單次的值,并且需要注意它的返回并不是結(jié)束整個(gè)函數(shù)。
import 'dart:io';
void main() {
final numbers = countValue(10);
for (var value in numbers) {
print('$value');
}
}
Iterable<int> countValue(int max) sync* {//sync*告訴Dart這個(gè)函數(shù)是一個(gè)按需生產(chǎn)值的同步生成器函數(shù)
for (int i = 0; i < max; i++) {
yield i; //yield關(guān)鍵字每次返回單次的值
sleep(Duration(seconds: 1));
}
}
輸出結(jié)果:
2.2 異步生成器(「Asynchronous」 Generator)
異步生成器函數(shù)需要配合 async* 關(guān)鍵字和 yield 關(guān)鍵字,最終返回的是一個(gè) Stream 對(duì)象,需要注意的是「生成Stream也是一個(gè)單一訂閱模型的Stream,」 也就是說不能同時(shí)存在多個(gè)訂閱者監(jiān)聽否則會(huì)出現(xiàn)異常,如果要實(shí)現(xiàn)支持多個(gè)監(jiān)聽者通過 asBroadcastStream 轉(zhuǎn)換成一個(gè)廣播訂閱模型的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這個(gè)函數(shù)是生成異步事件流的異步生成器函數(shù)
for (int i = 0; i < max; i++) {
yield i;
sleep(Duration(seconds: 1));
}
}
輸出結(jié)果:
2.3 yield關(guān)鍵字
「yield關(guān)鍵字」在生成器函數(shù)中是用于依序生成每一個(gè)值,它有點(diǎn)類似return語句,但是和return語句不一樣的是執(zhí)行一次yield并不會(huì)結(jié)束整個(gè)函數(shù)。相反它每次只提供單個(gè)值,并掛起(注意不是阻塞)等待調(diào)用者請(qǐng)求下一個(gè)值,然后它就會(huì)再執(zhí)行一遍,比如上述例子for循環(huán)中,「每一次循環(huán)執(zhí)行都會(huì)觸發(fā)yield執(zhí)行一次返回每次的單值并且進(jìn)入下一次循環(huán)的等待」。
Iterable<int> countValue(int max) sync* {//sync*告訴Dart這個(gè)函數(shù)是一個(gè)按需生產(chǎn)值的同步生成器函數(shù)
for (int i = 0; i < max; i++) {
yield i; //每執(zhí)行一次循環(huán)就會(huì)觸發(fā)當(dāng)前次yield生成值,然后進(jìn)入下一次的等待
sleep(Duration(seconds: 1));
}
}
2.4 yield* 關(guān)鍵字
yield關(guān)鍵字是用于循環(huán)中生產(chǎn)值后跟著一個(gè)具體對(duì)象或者值,但是yield后面則是跟著一個(gè)函數(shù),一般會(huì)跟著遞歸函數(shù),通過它就能實(shí)現(xiàn)類似二次遞歸函數(shù)功能。雖然yield關(guān)鍵字也能實(shí)現(xiàn)一個(gè)二次遞歸函數(shù)但是比較復(fù)雜,但是如果使用yield關(guān)鍵字就會(huì)更加簡(jiǎn)單。
//這是使用yield實(shí)現(xiàn)二次遞歸函數(shù)
Iterable naturals(n) sync* {
if (n > 0) {
yield n;
for (int i in naturals(n-1)) {
yield i;
}
}
}
//這是使用yield*實(shí)現(xiàn)的二次遞歸函數(shù)
Iterable naturals(n) sync* {
if ( n > 0) {
yield n;
yield* naturals(n-1);
}
}
2.5 await for
關(guān)于await for在Stream那一篇文章中已經(jīng)有說到,對(duì)于一個(gè)同步的迭代器Iterable而言我們可以使用for-in循環(huán)來遍歷每個(gè)元素,而對(duì)于一個(gè)異步的Stream可以很好地使用await for來遍歷每個(gè)事件元素。
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. 如何使用生成器函數(shù)(Generator)
3.1 sync* + yield實(shí)現(xiàn)同步生成器
void main() {
final Iterable<int> sequence = countDownBySync(10);
print('start');
sequence.forEach((element) => print(element));
print('end');
}
Iterable<int> countDownBySync(int num) sync* {//sync*
while (num > 0) {
yield num--;//yield返回值
}
}
輸出結(jié)果:
3.2 async* + yield實(shí)現(xiàn)異步生成器
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 num) async* {//async*表示異步返回一個(gè)Stream
while (num > 0) {
yield num--;
}
}
輸出結(jié)果:
3.3 sync* + yield*實(shí)現(xiàn)遞歸同步生成器
void main() {
final Iterable<int> sequence = countDownBySyncRecursive(10);
print('start');
sequence.forEach((element) => print(element));
print('end');
}
Iterable<int> countDownBySyncRecursive(int num) sync* {
if (num > 0) {
yield num;
yield* countDownBySyncRecursive(num - 1);//yield*后跟一個(gè)遞歸函數(shù)
}
}
輸出結(jié)果:
3.4 async* + yield*實(shí)現(xiàn)遞歸異步生成器
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 num) async* {
if (num > 0) {
yield num;
yield* countDownByAsyncRecursive(num - 1);//yield*后跟一個(gè)遞歸函數(shù)
}
}
輸出結(jié)果:
4. 生成器函數(shù)(Generator)使用場(chǎng)景
關(guān)于生成器函數(shù)使用在開發(fā)過程其實(shí)并不多,但是也不是說關(guān)于生成器函數(shù)使用場(chǎng)景就沒有了,否則這篇文章就沒啥意義了。實(shí)際上如果有小伙伴接觸Flutter開發(fā)中其中有一個(gè)「Bloc的狀態(tài)管理框架」,可以發(fā)現(xiàn)它里面大量地使用了異步生成器函數(shù),一起來看下:
class CountBloc extends Bloc<CounterEvent, int> {
@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;
}
}
}
其實(shí)除了上面所說Bloc狀態(tài)管理框架中使用到的場(chǎng)景,可能還有一種場(chǎng)景那就是異步二次函數(shù)遞歸場(chǎng)景,比如實(shí)現(xiàn)某個(gè)動(dòng)畫軌跡計(jì)算,實(shí)際上都是通過一些二次函數(shù)計(jì)算模擬出來,所以這時(shí)候生成器遞歸函數(shù)是不是就可以派上用場(chǎng)了。雖然生成器函數(shù)使用場(chǎng)景不是很頻繁,但是需要做到某個(gè)特定場(chǎng)景第一時(shí)間想到用它可以更加簡(jiǎn)單的實(shí)現(xiàn)就可以了。
5. 總結(jié)
到這里有關(guān)Flutter異步編程系列就結(jié)束了,下一個(gè)系列將進(jìn)入Dart注解+APT生成代碼技術(shù)專題。盡管生成器函數(shù)使用在開發(fā)過程其實(shí)并不多,但是它也作為Flutter異步中一部分,有一些特定場(chǎng)景如果能想到用它來解決,一定會(huì)事半功倍的。
感謝關(guān)注,熊喵先生愿和你在技術(shù)路上一起成長(zhǎng)!
