在 iOS 平臺實現新的異步解決方案 async/await

本文是 RJIterator 做者 @rkuiRJIterator 實現的詳細總結。分析了 ES7async/await 的實現原理,並依此詳細說明了在 iOS 中實現 async/await 的思路。具體的實現細節有不少值得借鑑的地方,歡迎你們一塊兒討論,也一塊兒來完善這個優秀的做品。javascript

知識小集是一個團隊公衆號,每週都會有原創文章分享,咱們的文章都會在公衆號首發。歡迎關注查看更多內容。java

async/awaitES7 提出的異步解決方案。對比回調鏈和 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/awaitiOS 平臺的一次實現過程,並給出了一個成果項目。安全

暫時繼續討論JavaScript

生成器與迭代器

要明白 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 調用將獲得結果 resultresult 對象包含兩個屬性:valuedonevalue 表示這次迭代獲得的結果值,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 並無執行完,因此 donefalse閉包

第 2 次調用 next,生成器 numbers 從上次中斷的位置恢復執行,繼續執行到下一個 yield 語句時,numbers 再次中斷,並將結果值 2 返回給迭代器,因爲 numbers 並無執行完,因此 donefalse

第 3 次調用 next,生成器 numbers 從上次中斷的位置恢復執行,繼續執行到下一個 yield 語句時,numbers 再次中斷,並將結果值 3 返回給迭代器,因爲 numbers 並無執行完,因此 donefalse

第 4 次調用 next,生成器 numbers 從上次中斷的位置恢復執行,此時已經是函數尾,numbers 將直接 return,因爲 numbers 已經執行完成,因此 donetrue。因爲 numbers 並無顯式地返回任何值,所以這次迭代 valueundefined.

到此迭代結束,此後經過此迭代器的 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個文件讀取完畢
      });
    });
  });
}
複製代碼

基於前面起到的"經過迭代器與生成器交換數據"的特性,拓展出新思路:

  1. 把讀取文件這個動做封裝爲一個異步操做,經過 callback 輸出結果 :errdata
  2. read3Files 改變爲生成器,內部經過 yield 返回異步操做給執行器(執行器第3步描述);
  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語法糖

到了 ES7async/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 表示等待異步操做的實際結果。

至此,JSasync/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 返回的對象,或者nildone 指示是否迭代結束。

根據前面描述的生成器特性,那麼在 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 調用會中斷當前執行流,並指望未來可以從中斷處繼續恢復執行,那麼一定要在觸發中斷時保存現場,包括:

  1. 當前指令地址
  2. 當前寄存器信息,包括當前棧幀棧頂

並且中斷後到恢復的這段時間內,應當確保 yield 以及生成器 generator 的棧幀不會被銷燬。

而恢復執行的過程是保存現場的逆過程,即恢復相關寄存器,並跳轉到保存的指令地址處繼續執行。

上述過程描述起來看似簡單,可是若是要本身寫彙編代碼去保存與恢復現場,並適配各類平臺,要保證穩定性仍是很難的,好在有C標準庫提供的現成利器:setjmp/longjmp

setjmp/longjmp 能夠實現跨函數的遠程跳轉,對比 goto 只能實現函數內跳轉,setjmp/longjmp 實現遠程跳轉基於的就是保存現場與恢復現場的機制,很是符合此處的需求。

實現思路

根據前面對生成器,迭代器的定義及需求推敲整理出以下的實現思路:

  1. 迭代器經過 next 方法與生成器進行交互時,在 next 方法內部會將控制流切換到生成器,生成器經過調用 yield 設置傳給迭代器的返回值,並將執行流切換回到 next 方法;
  2. 切回 next 方法後,拿到這個值,正常返回給調用者;
  3. 爲了確保 next 方法返回後,生成器的執行棧不被銷燬,所以生成器方法的執行須要在一個不被釋放的新棧上進行;
  4. 雖然 next 主要經過恢復現場方式切入生成器,可是首次仍是須要經過函數調用方式來進入生成器,經過中介 wrapper 調用生成器的方式,能夠檢測到生成器執行結束的事件,而後 wrapper 再切回 next 方法,並設置 doneYES,迭代結束。

整個流程圖解以下:

乍一看好大一坨,可是隻要跟着箭頭流程走,思路將很快理清。

根據此思路,爲迭代器新增屬性以下:

@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 本身的調用棧上調用 wrapperwrapper 再調用生成器,那麼 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_DONEnext 被恢復後拿到這個值就知道生成器執行結束,迭代該結束了。

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 輸出 errorvalue 便可。

同時 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 是我根據本文描述的思路實現的迭代器、生成器、yieldasync/await 的完整項目,歡迎交流與探討。

歡迎加入知識小集討論羣

另外,咱們開了微信羣,方便你們溝通交流技術。目前知識小集1號羣已滿,新開了知識小集2號羣,有興趣的能夠加入。沒有iOS限制,移動開發的同窗都歡迎。因爲微信羣掃碼加羣有100的限制,因此若是掃碼沒法加入的話,能夠先加微信號 coldlight_hh 或者 wsy9871,再拉進羣哈。

相關文章
相關標籤/搜索