阅读 1996

在 iOS 平台实现新的异步解决方案 async/await

本文是 RJIterator 作者 @rkuiRJIterator 实现的详细总结。分析了 ES7async/await 的实现原理,并依此详细说明了在 iOS 中实现 async/await 的思路。具体的实现细节有很多值得借鉴的地方,欢迎大家一起讨论,也一起来完善这个优秀的作品。

知识小集是一个团队公众号,每周都会有原创文章分享,我们的文章都会在公众号首发。欢迎关注查看更多内容。

async/awaitES7 提出的异步解决方案。对比回调链和 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/awaitiOS 平台的一次实现过程,并给出了一个成果项目。

暂时继续讨论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 调用将得到结果 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,再拉进群哈。

关注下面的标签,发现更多相似文章
评论
说说你的看法