本文是
RJIterator
作者@rkui
對RJIterator
實現的詳細總結。分析了ES7
中async/await
的實現原理,並依此詳細說明了在iOS
中實現async/await
的思路。具體的實現細節有很多值得借鑑的地方,歡迎大家一起討論,也一起來完善這個優秀的作品。
知識小集是一個團隊公眾號,每週都會有原創文章分享,我們的文章都會在公眾號首發。歡迎關注檢視更多內容。
async/await
是 ES7
提出的非同步解決方案。對比回撥鏈和 Promise.then
鏈的非同步程式設計模式,基於 async/await
我們可以以同步風格編寫非同步程式碼,程式邏輯清晰明瞭。
如順序讀取三個檔案:
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
鏈需要將邏輯分散在過多的程式碼塊:
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
平臺的一次實現過程,並給出了一個成果專案。
暫時繼續討論JavaScript
生成器與迭代器
要明白 async/await
的機制及運用,需從生成器與迭代器逐步說起。在 ES6
中,生成器是一個函式,和普通函式的有以下幾個區別:
- 生成器函式
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();
複製程式碼
建立迭代器並開始如下迭代過程:
- 第 1 次迭代,生成器開始執行,到達第一個
yield
語句時,返回value = want age, done = false
給迭代器, 並中斷。
let result = iterator.next();
console.log(result);
//輸出 => { value: `want age`, done: false }
複製程式碼
- 第 2 次迭代,給
next
傳參28
,生成器從上次中斷的地方恢復執行,並將28
作為甦醒後yield
的內部返回值賦給age
;然後生成器繼續執行,再次遇到yield
,返回value = want name, done = false
給迭代器,並中斷。
result = iterator.next(28);
console.log(result);
//輸出 => { value: `want name`, done: false }
複製程式碼
- 第 3 次迭代,給
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
語法向迭代器返回值,而且可以多次返回,並多次恢復執行,有別於傳統函式”返回便消亡”的特點; - 可以通過迭代器向生成器內部傳值,傳入的值將作為本次生成器 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
看起來好大一坨,並不優雅。實際上執行器當然是複用的,不用每次都實現執行器。
async/await語法糖
到了 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
的來龍去脈已基本描述完畢。
回到iOS
光描述 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;
});
複製程式碼
更好聽的名字: async/await
將上一步實現的的執行器 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,再拉進群哈。