KnightSama‘s Blog

vuePress-theme-reco KnightSama    2021
KnightSama‘s Blog KnightSama‘s Blog

Choose mode

  • dark
  • auto
  • light
首页
分类
  • iOS
  • 集锦
  • JavaScript
  • Github
  • Python
标签
时间线
GitHub
author-avatar

KnightSama

27

文章

14

标签

首页
分类
  • iOS
  • 集锦
  • JavaScript
  • Github
  • Python
标签
时间线
GitHub
  • 在Objective-C中使用协程

    • 什么是协程
      • 如何在Objective-C中使用协程
        • coobjc 的使用
          • 安装
          • 创建协程
          • Await
          • 取消操作
          • 错误处理
          • 在协程中暂停
          • 生成器
          • Actor
          • 元组

      在Objective-C中使用协程

      vuePress-theme-reco KnightSama    2021

      在Objective-C中使用协程


      KnightSama 2019-03-15 iOS Objective-C

      # 什么是协程

      协程是一种在非抢占式多任务场景下生成可以在特定位置挂起和恢复执行入口的程序组件。协程是一个特殊的函数,它可以在某个地方挂起,并且可以重新在挂起处继续运行。 一个进程可以包含多个线程,一个线程也可以包含多个协程。也就是说,一个线程内可以有多个那样的特殊函数在运行。但是一个线程内的多个协程的运行是串行的。如果有多核 CPU 的话,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内的多个协程却绝对串行的,当一个协程运行时,其他协程必须挂起。

      # 如何在 Objective-C 中使用协程

      很多语言自带对协程的支持,例如 Lua、Python 等,但是 Objective-C 并没有自带这种支持,因此在 iOS 的开发过程中很难使用协程,直到阿里开源了 coobjc 。 coobjc 是阿里的开源库,它为 Objective-C 与 Swift 提供了协程功能。通过使用协程我们可以将很多异步逻辑通过同步的方式来编写,大大提高了可读性。此外与多线程相比,协程不需要内核级的线程切换,大大提高了性能,同时可以减少锁的使用,提高应用的整体性能。

      # coobjc 的使用

      # 安装

      使用 cocopods 引入主要的文件,Objective-C 使用 coobjc,Swift 使用 coswift,cokit 内提供了对 Foundation 和 UIKit 框架内系统原生类的协程支持

      pod "coobjc"
      pod "cokit"
      
      1
      2

      # 创建协程

      我们可以使用下面两个方法创建协程

      // 在当前队列创建协程
      co_launch(^{
          // do something
      });
      
      // 在指定的队列q创建协程
      co_launch_onqueue(q, ^{
          // do something
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      使用示例

        co_launch(^{
            NSLog(@"aaaa");
        });
        
        co_launch_onqueue(dispatch_queue_create(0, 0), ^{
            NSLog(@"bbbb");
        });
        
        NSLog(@"cccc");
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        cccc
        bbbb
        aaaa
        
        1
        2
        3

        # Await

        我们可以使用 Promises 或者 Channel 创建异步方法并通过 await 在协程中使用。

          // 使用Promises
          // 通过COPromise来创建异步事件
          - (COPromise *)doSomethingAsync{
              return [COPromise promise:^(COPromiseFullfill  _Nonnull fullfill, COPromiseReject  _Nonnull reject) {
                  // 模拟异步的事件
                  dispatch_async(dispatch_queue_create(0, 0), ^{
                      // do something
                      NSLog(@"异步操作");
                      // 定义错误
                      NSError *error = nil;
                      if (error) {
                          reject(error); // 错误的回调
                      } else {
                          fullfill(@"result"); // 正确结果的回调
                      }
                  });
              }];
          }
          
          co_launch(^{
              NSLog(@"------start------");
              id ret = await([self doSomethingAsync]);
              // 获取可能存在的错误
              NSError *error = co_getError();
              if (error) {
                  NSLog(@"%@",error);
              }else{
                  NSLog(@"output:%@",ret);
              }
              NSLog(@"------end------");
          });
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          ------start------
          异步操作
          output:result
          ------end------
          
          1
          2
          3
          4
            // 使用Channel
            // 通过Channel来创建事件
            - (COChan *)doSomethingAsync{
                COChan *chan = [COChan chan];
                co_launch(^{
                    // do something
                    // 发送正确的结果
                    // send方法需要在协程中使用
                    [chan send:@"result"];
                });
                return chan;
            }
            
            co_launch(^{
                NSLog(@"------start------");
                id ret = await([self doSomethingAsync]);
                // 获取结果
                NSLog(@"output:%@",ret);
                NSLog(@"------end------");
            });
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            ------start------
            output:result
            ------end------
            
            1
            2
            3

            # 取消操作

            协程可以通过 cancel 方法取消

              COCoroutine *coroutine = co_launch(^{
                  NSLog(@"------start------");
                  co_delay(1.0); // sleep
                  if(co_isCancelled()){
                      // Do some cleaning operations
                      NSLog(@"------cancel------");
                      return;
                  }
                  NSLog(@"------end------");
              });
              
              [coroutine cancel];
              
              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              12
              ------start------   
              ------cancel------   
              
              1
              2

              # 错误处理

              协程中通过 co_getError 来获取错误信息

                // 通过COPromise来创建异步事件
                - (COPromise *)doSomethingAsync{
                    return [COPromise promise:^(COPromiseFullfill  _Nonnull fullfill, COPromiseReject  _Nonnull reject) {
                        dispatch_async(dispatch_queue_create(0, 0), ^{
                            // do something
                            NSLog(@"异步操作");
                            // 定义错误
                            NSError *error = [NSError errorWithDomain:@"自定义错误" code:999999 userInfo:nil];
                            if (error) {
                                dispatch_async(dispatch_get_main_queue(), ^{
                                    reject(error); // 错误的回调
                                });
                            } else {
                                fullfill(@"result"); // 正确结果的回调
                            }
                        });
                    }];
                }
                
                co_launch(^{
                    NSLog(@"------start------");
                    id ret = await([self doSomethingAsync]);
                    // 获取可能存在的错误
                    NSError *error = co_getError();
                    if (error) {
                        NSLog(@"%@",error);
                    }else{
                        NSLog(@"output:%@",ret);
                    }
                    NSLog(@"------end------");
                });
                
                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                12
                13
                14
                15
                16
                17
                18
                19
                20
                21
                22
                23
                24
                25
                26
                27
                28
                29
                30
                31
                ------start------
                异步操作
                Error Domain=自定义错误 Code=999999 "(null)"
                ------end------
                
                1
                2
                3
                4

                测试发现 reject 必须与调用的协程在相同的线程队列中运行错误才能正确的获取,否则会抛出一个异常

                # 在协程中暂停

                在协程中可以使用 co_delay 来暂停当前协程,暂停当前的协程不会影响其他协程的运行

                  // 中间有暂停的协程
                  co_launch(^{
                      NSLog(@"------start------");
                      co_delay(2.0); // 当前协程暂停2s
                      NSLog(@"------end------");
                  });
                  
                  // 另一个协程
                  co_launch(^{
                      NSLog(@"前一个协程暂停,这个协程可以运行");
                  });
                  
                  1
                  2
                  3
                  4
                  5
                  6
                  7
                  8
                  9
                  10
                  11
                  ------start------
                  前一个协程暂停,这个协程可以运行
                  ------end------
                  
                  1
                  2
                  3

                  # 生成器

                  生成器 (Generator) 是一次生成一个值的特殊类型函数。可以将其视为可恢复函数。简单的说就是在函数的执行过程中,yield 语句会把你需要的值返回给调用生成器的地方,然后退出函数,下一次调用生成器函数的时候又从上次中断的地方开始执行,而生成器内的所有变量参数都会被保存下来供下一次使用。

                  我们可以通过 co_sequence 来创建生成器,并在协程中使用它。

                    // 创建生成器
                    COCoroutine *generator = co_sequence(^{
                        int index = 0;
                        while(co_isActive()){
                            yield_val(@(index)); // 调用生成器将index的值返回
                            index++; // 每次调用生成器后将index的值加1
                        }
                    });
                    
                    co_launch(^{
                        // 循环调用生成器5次
                        for (NSInteger index = 0; index < 5; index++) {
                            NSLog(@"生成器返回:%@",[generator next]);
                        }
                    });
                    
                    1
                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    生成器返回:0
                    生成器返回:1
                    生成器返回:2
                    生成器返回:3
                    生成器返回:4
                    
                    1
                    2
                    3
                    4
                    5

                    # Actor

                    Actor 是一种并发模型,Actor 模型内部的状态由它自己维护,即它内部数据只能由它自己修改 (通过消息传递来进行状态修改),所以使用 Actors 模型进行并发编程可以很好地避免加锁等线程问题,Actor 由状态 (state)、行为 (Behavior) 和邮箱 (mailBox) 三部分组成。

                    状态 (state):Actor 中的状态指的是 Actor 对象的变量信息,状态由 Actor 自己管理,避免了并发环境下的锁和内存原子性等问题

                    行为 (Behavior):行为指定的是 Actor 中计算逻辑,通过 Actor 接收到消息来改变 Actor 的状态

                    邮箱 (mailBox):邮箱是 Actor 和 Actor 之间的通信桥梁,邮箱内部通过 FIFO 消息队列来存储发送方 Actor 消息,接受方 Actor 从邮箱队列中获取消息

                    在 coobjc 中通过 co_actor_onqueue 在指定队列中创建 Actor, 通过 sendMessage 向 Actor 发送消息

                      // 创建一个Actor
                      COActor *actor = co_actor_onqueue(dispatch_queue_create(0, 0), ^(COActorChan * _Nonnull event) {
                          for (COActorMessage *message in event) {
                              // 打印Actor接受到的消息
                              NSLog(@"%@",message.type);
                          }
                      });
                      
                      // 向actor发送消息
                      [actor sendMessage:@"message1"];
                      
                      1
                      2
                      3
                      4
                      5
                      6
                      7
                      8
                      9
                      10
                      message1
                      
                      1

                      # 元组

                      元组是一种数据类型,他是一组数据的集合,这些数据可以是不同的数据类型。 通过 co_tuple 创建元组

                      COTuple *tuple = co_tuple(nil,@"1",@(2),@[@1,@2],@{@"key":@"value"});
                      
                      1

                      可以像数组那样获取其中的数据

                      NSArray *tmpArr = tuple[3];
                      
                      1

                      可以通过 co_unpack 来解包元组

                      NSString *stringValue;
                      NSNumber *numberValue;
                      NSArray *arrayValue;
                      co_unpack(&stringValue,&numberValue,&arrayValue) = co_tuple(@"1",@(2),@[@1,@2]);
                      
                      1
                      2
                      3
                      4
                      欢迎来到 KnightSama‘s Blog
                      看板娘