本文是
RJIterator
做者@rkui
對RJIterator
實現的詳細總結。分析了ES7
中async/await
的實現原理,並依此詳細說明了在iOS
中實現async/await
的思路。具體的實現細節有不少值得借鑑的地方,歡迎你們一塊兒討論,也一塊兒來完善這個優秀的做品。javascript
知識小集是一個團隊公衆號,每週都會有原創文章分享,咱們的文章都會在公衆號首發。歡迎關注查看更多內容。java
async/await
是 ES7
提出的異步解決方案。對比回調鏈和 Promise.then
鏈的異步編程模式,基於 async/await
咱們能夠以同步風格編寫異步代碼,程序邏輯清晰明瞭。git
如順序讀取三個文件:github
function readFile(name) {
return new Promise((resolve, reject) => {
//異步讀取文件
fs.readFile(name, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
async function read3Files() {
try {
//讀取第1個文件
let data1 = await readFile('file1.txt');
//讀取第2個文件
let data2 = await readFile('file2.txt');
//讀取第3個文件
let data3 = await readFile('file3x.txt');
//3個文件讀取完畢
} catch (error) {
//讀取出錯
}
}
複製代碼
讀取文件自己是異步操做,而在要求順序讀取的前提下,基於 callback
實現將形成很深的回調嵌套:編程
function readFile(name, callback) {
//異步讀取文件
fs.readFile(name, (err, data) => {
callback(err, data);
});
}
function read3Files() {
//讀取第1個文件
readFile('file1.txt', (err, data) => {
//讀取第2個文件
readFile('file2.txt', (err, data) => {
//讀取第3個文件
readFile('file3.txt', (err, data) => {
//3個文件讀取完畢
});
});
});
}
複製代碼
基於 Promise.then
鏈須要將邏輯分散在過多的代碼塊:promise
function readFile(name) {
return new Promise((resolve, reject) => {
//異步讀取文件
fs.readFile(name, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
function read3Files() {
//讀取第1個文件
readFile('file1.txt')
.then(data => {
//讀取第2個文件
return readFile('file2.txt');
})
.then(data => {
//讀取第3個文件
return readFile('file3.txt');
})
.then(data => {
//3個文件讀取完畢
})
.catch(error => {
//讀取出錯
});
}
複製代碼
對比可見,aync/await
模式的優雅與簡潔。接觸完畢後,深感若是在 iOS
項目中也能像 JS
這般編寫異步代碼也是極好。通過研究發現要在 iOS
平臺實現這些特性其實並非很困難,所以本文主旨即是描述 async/await
在 iOS
平臺的一次實現過程,並給出了一個成果項目。安全
要明白 async/await
的機制及運用,需從生成器與迭代器逐步提及。在 ES6
中,生成器是一個函數,和普通函數的有如下幾個區別:bash
function
關鍵字後多了個 *
:function *numbers() {}
複製代碼
yield
語法屢次返回值function *numbers() {
yield 1;
yield 2;
yield 3;
}
複製代碼
next
方法控制生成器的執行:let iterator = numbers();
let result = iterator.next();
複製代碼
每一次 next
調用將獲得結果 result
,result
對象包含兩個屬性:value
和 done
。value
表示這次迭代獲得的結果值,done
表示是否迭代結束。好比:微信
function *numbers() {
yield 1;
yield 2;
yield 3;
}
let iterator = numbers();
//第1次迭代
let result = iterator.next();
console.log(result);
//輸出 => { value: 1, done: false }
//第2次迭代
result = iterator.next();
console.log(result);
//輸出 => { value: 2, done: false }
//第3次迭代
result = iterator.next();
console.log(result);
//輸出 => { value: 3, done: false }
//第4次迭代
result = iterator.next();
console.log(result);
//輸出 => { value: undefined, done: true }
複製代碼
第 1 次調用 next
,生成器 numbers
開始執行。執行到第一個 yield
語句時,numbers
將中斷,並將結果值 1
返回給迭代器。因爲 numbers
並無執行完,因此 done
爲 false
。閉包
第 2 次調用 next
,生成器 numbers
從上次中斷的位置恢復執行,繼續執行到下一個 yield
語句時,numbers
再次中斷,並將結果值 2
返回給迭代器,因爲 numbers
並無執行完,因此 done
爲 false
。
第 3 次調用 next
,生成器 numbers
從上次中斷的位置恢復執行,繼續執行到下一個 yield
語句時,numbers
再次中斷,並將結果值 3
返回給迭代器,因爲 numbers
並無執行完,因此 done
爲 false
。
第 4 次調用 next
,生成器 numbers
從上次中斷的位置恢復執行,此時已經是函數尾,numbers
將直接 return
,因爲 numbers
已經執行完成,因此 done
爲 true
。因爲 numbers
並無顯式地返回任何值,所以這次迭代 value
爲 undefined
.
到此迭代結束,此後經過此迭代器的 next
方法,都將獲得相同的結果 { value: undefined, done: true }
。
function *hello() {
let age = yield 'want age';
let name = yield 'want name';
console.log(`Hello, my age: ${age}, name:${name}`);
}
let iterator = hello();
複製代碼
建立迭代器並開始以下迭代過程:
yield
語句時,返回 value = want age, done = false
給迭代器, 並中斷。let result = iterator.next();
console.log(result);
//輸出 => { value: 'want age', done: false }
複製代碼
next
傳參 28
,生成器從上次中斷的地方恢復執行,並將 28
做爲甦醒後 yield
的內部返回值賦給 age
;而後生成器繼續執行,再次遇到yield
,返回value = want name, done = false
給迭代器,並中斷。result = iterator.next(28);
console.log(result);
//輸出 => { value: 'want name', done: false }
複製代碼
next
傳參 'LiLei'
,生成器從上次中斷的地方恢復執行,並將 'LiLei'
做爲甦醒後 yield
的內部返回值賦給 name
;而後生成器繼續執行,打印 log
。Hello, my age: 28, name:LiLei
複製代碼
而後到達函數尾,完全結束生成器,並返回 value = undefined, done = true
給迭代器。
result = iterator.next('LiLei');
console.log(result);
//輸出 => { value: undefined, done: true }
複製代碼
可見經過迭代器可與生成器「互相交換數據」,生成器經過
yield
返回數據 A 給迭代器並中斷,而經過迭代器又能夠把數據 B 傳給生成器並讓yield
語句甦醒後以 B 做爲右值。這個特性是下一步"改進異步編程"的重要基礎。
至此已基本瞭解了生成器與迭代器的語法與運用,總結起來:
yield
語法向迭代器返回值,並且能夠屢次返回,並屢次恢復執行,有別於傳統函數"返回便消亡"的特色;回想本文開頭提到的讀取文件例子,若是以 callback
模式編寫:
function readFile(name, callback) {
//異步讀取文件
fs.readFile(name, (err, data) => {
callback(err, data);
});
}
function read3Files() {
//讀取第1個文件
readFile('file1.txt', (err, data) => {
//讀取第2個文件
readFile('file2.txt', (err, data) => {
//讀取第3個文件
readFile('file3.txt', (err, data) => {
//3個文件讀取完畢
});
});
});
}
複製代碼
基於前面起到的"經過迭代器與生成器交換數據"的特性,拓展出新思路:
callback
輸出結果 :err
和 data
;read3Files
改變爲生成器,內部經過 yield
返回異步操做給執行器(執行器第3步描述);read3Files
返回的異步操做,拿到異步操做後,發起該異步操做,獲得結果後再其「交換」給生成器 read3Files
內的 yield
即:
function readFile(name) {
//返回一個閉包做爲異步操做
return function(callback) {
fs.readFile(name, (err, data) => {
callback(err, data);
});
};
}
//執行器
function executor(generator) {
//建立迭代器
let iterator = generator();
//開始第一次迭代
let result = iterator.next();
let nextStep = function() {
//迭代還沒結束
if (!result.done) {
//從生成器拿到的是一個異步操做
if (typeof result.value === "function") {
//發起異步操做
result.value((err, data) => {
if(err) {
//在生成器內部引起異常
iterator.throw(err);
}
else {
//獲得結果值,傳給生成器
result = iterator.next(data);
//繼續下一步迭代
nextStep();
}
});
}
//從生成器拿到的是一個普通對象
else {
//什麼都不作,直接傳回給生成器
result = iterator.next(result.value);
//繼續下一步迭代
nextStep();
}
}
};
//開始後續迭代
nextStep();
}
複製代碼
而 read3Files
改進爲:
executor(function *() {
try {
//讀取第1個文件
let data1 = yield readFile('file1.txt');
//讀取第2個文件
let data2 = yield readFile('file2x.txt');
//讀取第3個文件
let data3 = yield readFile('file3.txt');
} catch (e) {
//讀取出錯
}
});
複製代碼
此時已經把 callback
模式改進爲同步模式。
暫且把傳給執行器的生成器函數叫作"異步函數",執行過程總結起來就是:
異步函數但凡遇到異步操做,就經過 yield
交給執行器;執行器但凡拿到異步操做,就發起該操做,拿到實際結果後再將其交換給異步函數。那麼在異步函數內,就能夠同步風格編寫異步代碼,由於有了執行器在背後運做,異步函數內的 yield
就具備了「你給我異步操做,我還你實際結果」的能力。
Promoise
一樣可做爲異步操做:
function readFile(name) {
//返回一個Promise做爲異步操做
return new Promise((resolve, reject) => {
fs.readFile(name, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
複製代碼
在執行器中新增識別 Promise
的代碼:
function executor(generator) {
//建立迭代器
let iterator = generator();
//開始第一次迭代
let result = iterator.next();
let nextStep = function() {
//迭代還沒結束
if (!result.done) {
if (typeof result.value === "function") {
....
}
//從生成器拿到的是一個Promise異步操做
else if (result.value instanceof Promise) {
//執行該Promise
result.value.then(data => {
//獲得結果值,傳給生成器
result = iterator.next(data);
//繼續下一步迭代
nextStep();
}).catch(err => {
//在生成器內部引起異常
iterator.throw(err);
});
}
else {
...
}
}
};
...
}
複製代碼
到此已經成功把異步編程化爲同步風格,但或許有個疑問:這個例子卻是化異步爲同步風格了,可是那個執行器 executor
看起來好大一坨,並不優雅。實際上執行器固然是複用的,不用每次都實現執行器。
到了 ES7
,async/await
終於出來。async/await
是上述執行器,生成器模式的語法糖,運用 async/await
,不再須要每次都定義生成器做爲異步函數,而後顯式傳給執行器,只要簡單在函數定義前增長 async
,表示這是一個異步函數,內部將用 await
來等待異步結果:
async function foo() {
let value = await 異步操做;
let value = await 異步操做;
let value = await 異步操做;
let value = await 異步操做;
}
複製代碼
如讀取文件例子:
async function read3Files() {
//讀取第1個文件
let data1 = await readFile('file1.txt');
//讀取第2個文件
let data2 = await readFile('file2.txt');
//讀取第3個文件
let data3 = await readFile('file3x.txt');
//3個文件讀取完畢
}
複製代碼
而後直接調用便可:
read3Files();
複製代碼
async
表示該函數內部包含異步操做,須要把它交給內置執行器;
await
表示等待異步操做的實際結果。
至此,JS
下 async/await
的前因後果已基本描述完畢。
光描述 JS
生成器,迭代器,async/await
就花了大量篇幅,由於在 iOS
上將以它們的 JS
特性爲目標,最終實現 OC
版的迭代器,生成器,async/await
。
暫時無需在乎怎麼實現,既然是之前面描述的特性爲目標,則能夠根據其特性先作以下定義:
先定義 yield
以下:
id yield(id value);
複製代碼
yield
接受一個對象 value
做爲返回給迭代器的值,同時返回一個迭代器設置的新值或者本來值 value
。
每次迭代的 Result
:
@interface Result: NSObject
@property (nonatomic, strong, readonly) id value;
@property (nonatomic, readonly) BOOL done;
@end
複製代碼
value
表示迭代的結果,爲 yield
返回的對象,或者nil
。done
指示是否迭代結束。
根據前面描述的生成器特性,那麼在 OC
裏,生成器首先應該是一個 C函數/OC方法/block
,且內部經過調用 yield
來返回結果給迭代器:
void generator() {
yield(value);
yield(value);
}
- (void)generator {
yield(value);
yield(value);
}
^{
yield(value);
yield(value);
}
複製代碼
實際上不管是 OC
方法,仍是 block
,底層調用時都與調用 C
函數無異。
只是調用
block
會默認以block
結構體地址做爲第一個隱含參數;調用方法會以對象自身self
,和選擇器_cmd
做爲前兩個隱含參數
因此只要實現了 C
函數版生成器,其實現機制將也無縫適用於 OC
方法,block
。
迭代器定義:
@interface Iterator : NSObject
{
void (*_func)(void);
}
- (id)initWithFunc:(void (*)(void))func;
- (Result *)next;
- (Result *)next:(id)value;
@end
複製代碼
迭代器的建立沒法作到像 JS
同樣直接調用生成器便可建立,須要顯式建立:
void generator() {
yield(value);
yield(value);
}
Iterator *iterator = [[Iterator alloc] initWithFunc: generator];
複製代碼
而後就能夠像 JS
同樣調用 next
來進行迭代:
Result *result = iterator.next;
//迭代並傳值
Result *result = [iterator next: value];
複製代碼
根據需求,yield
調用會中斷當前執行流,並指望未來可以從中斷處繼續恢復執行,那麼一定要在觸發中斷時保存現場,包括:
並且中斷後到恢復的這段時間內,應當確保
yield
以及生成器generator
的棧幀不會被銷燬。
而恢復執行的過程是保存現場的逆過程,即恢復相關寄存器,並跳轉到保存的指令地址處繼續執行。
上述過程描述起來看似簡單,可是若是要本身寫彙編代碼去保存與恢復現場,並適配各類平臺,要保證穩定性仍是很難的,好在有C標準庫提供的現成利器:setjmp/longjmp
。
setjmp/longjmp
能夠實現跨函數的遠程跳轉,對比 goto
只能實現函數內跳轉,setjmp/longjmp
實現遠程跳轉基於的就是保存現場與恢復現場的機制,很是符合此處的需求。
根據前面對生成器,迭代器的定義及需求推敲整理出以下的實現思路:
next
方法與生成器進行交互時,在 next
方法內部會將控制流切換到生成器,生成器經過調用 yield
設置傳給迭代器的返回值,並將執行流切換回到 next
方法;next
方法後,拿到這個值,正常返回給調用者;next
方法返回後,生成器的執行棧不被銷燬,所以生成器方法的執行須要在一個不被釋放的新棧上進行;next
主要經過恢復現場方式切入生成器,可是首次仍是須要經過函數調用方式來進入生成器,經過中介 wrapper
調用生成器的方式,能夠檢測到生成器執行結束的事件,而後 wrapper
再切回 next
方法,並設置 done
爲 YES
,迭代結束。整個流程圖解以下:
乍一看好大一坨,可是隻要跟着箭頭流程走,思路將很快理清。
根據此思路,爲迭代器新增屬性以下:
@interface Iterator : NSObject
{
int *_ev_leave; //迭代器在next方法內保存的現場
int *_ev_entry; //生成器經過yield保存的現場
BOOL _ev_entry_valid; //指示生成器現場是否可用
void *_stack; //爲生成器新分配的棧
int _stack_size; //爲生成器新分配的棧大小
void (*_func)(void);//迭代器函數指針
BOOL _done; //是否迭代結束
id _value; //生成器經過yield傳回的值
}
- (id)initWithFunc:(void (*)(void))func;
- (Result *)next;
- (Result *)next:(id)value;
@end
複製代碼
爲生成器分配新棧,正如前面所述,在迭代器和生成器的生命週期中,next
方法的每次迭代是要正常返回的,若是直接在 next
本身的調用棧上調用 wrapper
,wrapper
再調用生成器,那麼 next
返回後,生成器就算保護了寄存器現場,它的棧幀也被破壞了,再次恢復執行將產生沒法預料的結果。
//默認爲生成器分配256K的執行棧
#define DEFAULT_STACK_SIZE (256 * 1024)
- (id)init {
if (self = [super init]) {
//分配一塊內存做爲生成器的運行棧
_stack = malloc(DEFAULT_STACK_SIZE);
memset(_stack, 0x00, DEFAULT_STACK_SIZE);
_stack_size = DEFAULT_STACK_SIZE;
//jmp_buf類型來自C標準庫<setjmp.h>
_ev_leave = malloc(sizeof(jmp_buf));
memset(_ev_leave, 0x00, sizeof(jmp_buf));
_ev_entry = malloc(sizeof(jmp_buf));
memset(_ev_entry, 0x00, sizeof(jmp_buf));
}
return self;
}
複製代碼
實現 next
方法:
#define JMP_CONTINUE 1//生成器還可被繼續迭代
#define JMP_DONE 2//生成器已經執行結束,迭代器應該結束
- (Result *)next:(id)value {
if (_done) {
//迭代器已結束,則每次調用next都返回最後一次結果
return [Result resultWithValue:_value error:_error done:_done];
}
//保存next當前環境
int leave_value = setjmp(_ev_leave);
//非恢復執行
if (leave_value == 0) {
//已經設置了生成器進入點
if (_ev_entry_valid) {
//設置傳給生成器內yield的新值
if (value) {
self.value = value;
}
//直接從生成器進入點進入
longjmp(_ev_entry, JMP_CONTINUE);
}
else {
//生成器還沒保存過現場,從wrapper進入生成器
//next棧會銷燬,因此爲wrapper啓用新棧
intptr_t sp = (intptr_t)(_stack + _stack_size);
//預留安全空間,防止直接move [sp] 傳參 以及msgsend向上訪問堆棧
sp -= 256;
//對齊sp
sp &= ~0x07;
//直接修改棧指針sp,指向新棧
#if defined(__arm__)
asm volatile("mov sp, %0" : : "r"(sp));
#elif defined(__arm64__)
asm volatile("mov sp, %0" : : "r"(sp));
#elif defined(__i386__)
asm volatile("movl %0, %%esp" : : "r"(sp));
#elif defined(__x86_64__)
asm volatile("movq %0, %%rsp" : : "r"(sp));
#endif
//在新棧上調用wrapper,至此能夠認爲wrapper,以及生成器函數的運行棧和next無關
[self wrapper];
}
}
//從生成器內部恢復next
else if (leave_value == JMP_CONTINUE) {
//還能夠繼續迭代
}
//從生成器wrapper恢復next
else if (leave_value == JMP_DONE) {
//生成器結束,迭代完成
_done = YES;
}
return [RJResult resultWithValue:_value error:_error done:_done];
}
複製代碼
若是沒有中介wrapper,那麼迭代器返回將會形成崩潰,由於迭代器的運行棧和生成器是分開的,若是生成器內部執行return語句,返回後的棧空間將是未定義的,頗有可能形成非法內存訪問而崩潰.中介wrapper很好地解決了這個問題:
- (void)wrapper {
//調用生成器函數
if (_func) {
_func();
}
//從生成器返回,說明生成器徹底執行結束
self.value = nil;
//恢復next
longjmp(_ev_leave, JMP_DONE);
//不會到此
assert(0);
}
複製代碼
經過中介 wrapper
調用方式進入生成器,生成器最終返回後將正確返回到 wrapper
末尾繼續執行,而 wrapper
也就知道,此時生成器結束了,所以以 longjmp
方式恢復 next
的現場,並設置恢復值爲 JMP_DONE
,next
被恢復後拿到這個值就知道生成器執行結束,迭代該結束了。
yield
的實現就更加簡單,保存當前現場,將 value
值傳遞給迭代器對象,而後恢復迭代器next方法便可,而當後續從 next
恢復 yield
的現場後,yield
再取迭代器設置的新值返回給生成器內部,如此達到生成器與迭代器的數據交換:
id yield(id value) {
//獲取當前線程正在得到的生成器
Iterator *iterator = [IteratorStack top];
return [iterator yield: value];
}
- (id)yield:(id)value {
//設置生成器的現場已保護標誌
_ev_entry_valid = YES;
//現場保護
if (setjmp(_ev_entry) == 0) {
//現場保護完成
//給迭代器賦值
self.value = value;
//恢復迭代器next現場
longjmp(_ev_leave, JMP_CONTINUE);
}
//從迭代器next恢復此現場
//返回迭代器傳進來的新值,或者默認值value
return self.value;
}
複製代碼
這裏的 IteratorStack
是一個線程本地存儲的棧,棧頂永遠是當前線程正在活動的迭代器,具體實現能夠參考後邊給出的結果項目。
至此已經實現了 c
函數版本的生成器,簡單改變便可擴展到 OC
方法,block
。首先是迭代器須要支持新的初始化方法:
@interface Iterator : NSObject
{
int *_ev_leave; //迭代器在next方法內保存的現場
int *_ev_entry; //生成器經過yield保存的現場
BOOL _ev_entry_valid; //指示生成器現場是否可用
void *_stack; //爲生成器新分配的棧
int _stack_size; //爲生成器新分配的棧大小
void (*_func)(void);//迭代器函數指針
BOOL _done; //是否迭代結束
id _value; //生成器經過yield傳回的值
id _target;//生成器方法所在對象
SEL _selector;//生成器方法selector
id _block;//生成器block
NSMutableArray *_args;//傳遞給生成器的初始參數
}
- (id)initWithFunc:(void (*)(void))func;
- (id)initWithTarget:(id)target selector:(SEL)selector;
- (id)initWithBlock:(id)block;
- (Result *)next;
- (Result *)next:(id)value;
@end
複製代碼
wrapper
支持新的生成器調用方式:
- (void)wrapper {
if (_func) {
_func();
}
else if (_target && _selector) {
((void (*)(id, SEL))objc_msgSend)(_target, _selector);
}
else if (_block) {
((void (^)(void))_block)();
}
//從生成器返回,說明生成器徹底執行結束
self.value = nil;
//恢復next
longjmp(_ev_leave, JMP_DONE);
//不會到此
assert(0);
}
複製代碼
正如前面描述的 JS
下的改進方法,如今能夠用實現的生成器與迭代器來改進 iOS
的異步編程,且思路如出一轍。
首先定義異步操做爲以下閉包:
typedef void (^AsyncCallback)(id value, id error);
typedef void (^AsyncClosure)(AsyncCallback callback);
複製代碼
跟 JS
下的定義同樣,這種閉包內部可進行任何異步調用,最終以 callback
輸出 error
和 value
便可。
同時 PromiseKit
提供的 AnyPromise
也能夠做爲異步操做。
iOS
版本 readFile
:
- (AsyncClosure)readFileWithPath:(NSString *)path {
return ^(void (^resultCallback)(id value, id error)) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfFile:path];
resultCallback(data, [NSError new]);
});
};
}
- (AnyPromise *)readFileWithPath:(NSString *)path {
return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfFile:path];
adapter(data, [NSError new]);
});
}];
}
複製代碼
執行器 executor
:
@protocol LikePromise <NSObject>
- (id<LikePromise> __nonnull (^ __nonnull)(id __nonnull))then;
- (id<LikePromise> __nonnull(^ __nonnull)(id __nonnull))catch;
@end
void executor(dispatch_block_t block) {
Iterator * iterator = [[Iterator alloc] initWithBlock:block];
Result * __block result = nil;
dispatch_block_t __block step;
step = ^{
if (!result.done) {
id value = result.value;
//oc閉包
if ([value isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")] ||
[value isKindOfClass:NSClassFromString(@"__NSStackBlock__")] ||
[value isKindOfClass:NSClassFromString(@"__NSMallocBlock__")]
) {
((AsyncClosure)value)(^(id value, id error) {
dispatch_async(dispatch_get_main_queue(), ^{
[result release];
//將這次異步操做的結果包裝成Result,傳給生成器
result = [iterator next: [Result resultWithValue:value error:error done:NO]].retain;
step();
});
});
}
//AnyPromise
else if (NSClassFromString(@"AnyPromise") &&
[value isKindOfClass:NSClassFromString(@"AnyPromise")] &&
[value respondsToSelector:@selector(then)] &&
[value respondsToSelector:@selector(catch)]
) {
id <LikePromise> promise = (id <LikePromise>)value;
void (^__block then_block)(id) = NULL;
void (^__block catch_block)(id) = NULL;
then_block = Block_copy(^(id value){
if (then_block) { Block_release(then_block); then_block = NULL; }
if (catch_block) { Block_release(catch_block); catch_block = NULL; }
[result release];
result = [iterator next: [Result resultWithValue:value error:nil done:NO]].retain;
step();
});
catch_block = Block_copy(^(id error){
if (then_block) { Block_release(then_block); then_block = NULL; }
if (catch_block) { Block_release(catch_block); catch_block = NULL; }
[result release];
result = [iterator next: [Result resultWithValue:nil error:error done:NO]].retain;
step();
});
promise.then(then_block).catch(catch_block);
}
//普通對象
else {
Result *old_result = result;
result = [iterator next: old_result].retain;
[old_result release];
step();
}
}
else {
//執行過程結束
Block_release(step);
[result release];
[iterator release];
}
};
step = Block_copy(step);
dispatch_async(dispatch_get_main_queue(), ^{
result = iterator.next.retain;
step();
});
}
複製代碼
有了執行器 executor
,那麼順去讀取文件的例子在 iOS
下可以下實現:
executor(^{
Result *result1 = yield( [self readFileWithPath:@".../path1"] );
if (result1.error) {/*第1步出錯*/}
NSData *data1 = result1.value;
Result *result2 = yield( [self readFileWithPath:@".../path2"] );
f (result1.error) {/*第2步出錯*/}
NSData *data2 = result2.value;
Result *result3 = yield( [self readFileWithPath:@".../path3"] );
f (result1.error) {/*第3步出錯*/}
NSData *data3 = result3.value;
});
複製代碼
將上一步實現的的執行器 executor
更名爲 async
,新增 await
函數:
RJResult * await(id value);
RJResult * await(id value) {
return (Result *)yield(value);
}
複製代碼
其實 await
本質上就是 yield
。那麼讀取文件的例子就寫成:
async(^{
Result *result1 = await( [self readFileWithPath:@".../path1"] );
if (result1.error) {/*第1步出錯*/}
NSData *data1 = result1.value;
Result *result2 = await( [self readFileWithPath:@".../path2"] );
f (result1.error) {/*第2步出錯*/}
NSData *data2 = result2.value;
Result *result3 = await( [self readFileWithPath:@".../path3"] );
f (result1.error) {/*第3步出錯*/}
NSData *data3 = result3.value;
});
複製代碼
至此便在 iOS
平臺實現了 async/await
,且經過async/await
能夠化異步編程爲同步風格。單靠短短字面描述沒法面面俱到,好比 setjmp/longjmp
的原理及使用,函數調用過程與棧的聯繫,若有生疏要額外研究。本文旨在描述在 iOS
平臺上的一次對 async/await
的實現歷程,能夠經過下面的項目查看完整實現代碼。
RJIterator https://github.com/renjinkui2719/RJIterator
是我根據本文描述的思路實現的迭代器、生成器、yield
、async/await
的完整項目,歡迎交流與探討。
另外,咱們開了微信羣,方便你們溝通交流技術。目前知識小集1號羣已滿,新開了知識小集2號羣,有興趣的能夠加入。沒有iOS限制,移動開發的同窗都歡迎。因爲微信羣掃碼加羣有100的限制,因此若是掃碼沒法加入的話,能夠先加微信號 coldlight_hh 或者 wsy9871,再拉進羣哈。