阿里开源 iOS 协程开发框架 coobjc源码分析

栏目: IOS · 发布时间: 5年前

内容简介:昨天朋友圈被一篇文章(以下简称“coobjc介绍文章”)刷屏了:因此笔者想给大家普及普及协程的知识,运行一下协程的维基百科在这里:协程。引用里面的解释如下:

昨天朋友圈被一篇文章(以下简称“coobjc介绍文章”)刷屏了: 刚刚,阿里开源 iOS 协程开发框架 coobjc! 。可能大部分iOS开发者都直接懵逼了:

  • 什么是协程?
  • 协程的作用是什么?
  • 为什么要使用它?

因此笔者想给大家普及普及协程的知识,运行一下 coobjc 的Example,顺便分析一下 coobjc 源码。

分析

协程的维基百科在这里:协程。引用里面的解释如下:

协程是计算机程序的一类组件,推广了非抢先多任务的子程序,允许执行被挂起与被恢复。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程源自Simula和Modula-2语言,但也有其他语言支持。协程更适合于用来实现彼此熟悉的程序组件,如合作式多任务、异常处理、事件循环、迭代器、无限列表和管道。 根据高德纳的说法, 马尔文·康威于1958年发明了术语coroutine并用于构建汇编程序。

对,还是一知半解。但最起码我们了解到

coobjc
Objective-C

协程的作用其实在 coobjc 介绍文章中有提及,是为了优化 iOS 中的异步操作。解决了如下问题:

  • "嵌套地狱"
  • 错误处理复杂和冗长
  • 容易忘记调用 completion handler
  • 条件执行变得很困难
  • 从互相独立的调用中组合返回结果变得极其困难
  • 在错误的线程中继续执行
  • 难以定位原因的多线程崩溃
  • 锁和信号量滥用带来的卡顿、卡死

听起来是有点强大,最明显的好处是可以简化代码;并且在coobjc介绍文章也说道,性能也有所保障:当线程的数量级大于1000以上时, coobjc 的优势就会非常明显。为了证明文章的结论,我们就来运行一下 coobjc 源码好了。 这里 下载 coobjc 源码。 发现目录结构如下:

阿里开源 iOS 协程开发框架 coobjc源码分析
从目录结构看还是比较清晰的,根据 coobjc 介绍文章中提到的, coobjc

不但提供了基础的异步操作还提供了基于UIKit的封装。目录中

  • cokit 及其子目录提供的是基于UIKit层的 coobjc 封装
  • coobjc 目录是 coobjcObjective-C 版实现的源代码
  • coswift 目录是 coobjcSwift 版实现的源代码
  • Example 下有两个目录,一个是 Objective-C 的实现,一个是 Swift 版的实现的Demo

我们先分析一下 coobjcBaseExample 工程: 打开项目, pod update 一下即可运行,运行结果如下:

阿里开源 iOS 协程开发框架 coobjc源码分析

可以看到是个简单的列表页。

Tips 打开podfile可以发现里面有库 coobjc 以外,还有 SpectaExpecta 以及 OCMock 。这三个库这里不多做介绍了,大家只需要知道这是用于单元测试的。

我们先看一下这个列表的实现逻辑是什么样的。我们不难定位到页面位于 KMDiscoverListViewController 中,其网络请求(这里是电影列表)代码如下:

- (void)requestMovies
{
    co_launch(^{
        NSArray *dataArray = [[KMDiscoverSource discoverSource] getDiscoverList:@"1"];
        [self.refreshControl endRefreshing];
        
        if (dataArray != nil)
        {
            [self processData:dataArray];
        }
        else
        {
            [self.networkLoadingViewController showErrorView];
        }
    });
}
复制代码

这里很容易理解代码

NSArray *dataArray = [[KMDiscoverSource discoverSource] getDiscoverList:@"1"];
复制代码

是请求网络数据的,其实现如下:

- (NSArray*)getDiscoverList:(NSString *)pageLimit;
{
    NSString *url = [NSString stringWithFormat:@"%@&page=%@", [self prepareUrl], pageLimit];
    id json = [[DataService sharedInstance] requestJSONWithURL:url];
    NSDictionary* infosDictionary = [self dictionaryFromResponseObject:json jsonPatternFile:@"KMDiscoverSourceJsonPattern.json"];
    return [self processResponseObject:infosDictionary];
}
复制代码

以上代码也能猜出,

id json = [[DataService sharedInstance] requestJSONWithURL:url];
复制代码

这一行是做了网络请求,但是我们再点击进入类 DataServicerequestJSONWithURL 方法的实现的时候,发现已经看不懂了:

- (id)requestJSONWithURL:(NSString*)url CO_ASYNC{
    SURE_ASYNC
    return await([self.jsonActor sendMessage:url]);
}
复制代码

好吧。既然看不懂了,我们就从头开始学习,协程的含义以及使用。继而对 coobjc 源码进行分析。

协程入门

coobjc 介绍文章中有提到

  • 第一种:利用 glibcucontext 组件(云风的库)。
  • 第二种:使用汇编代码来切换上下文(实现C协程),原理同 ucontext
  • 第三种:利用 C语言 语法 switch-case 的奇淫技巧来实现(Protothreads)。
  • 第四种:利用了 C 语言的 setjmplongjmp
  • 第五种:利用编译器支持语法糖。

经过筛选最终选择了第二种。那我们来一个个分析,为什么 coobjc 摒弃了其他的方式。 首先我们看第一种, coobjc 介绍文章中提到 ucontext 在iOS中被废弃了,那如果不废弃,我们如何去使用 ucontext 呢?如下的一个Demo可以解释一下 ucontext 的用法:

#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>
 
int main(int argc, const char *argv[]){
    ucontext_t context;
    getcontext(&context);
    puts("Hello world");
    sleep(1);
    setcontext(&context);
    return 0;
}
复制代码

注:示例代码来自维基百科.

保存上述代码到example.c,执行编译命令:

gcc example.c -o example
复制代码

想想程序运行的结果会是什么样?

kysonzhu@ubuntu:~$ ./example 
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
^C
kysonzhu@ubuntu:~$
复制代码

上面是程序执行的部分输出,不知道是否和你想得一样呢?我们可以看到,程序在输出第一个“Hello world"后并没有退出程序,而是持续不断的输出“Hello world”。其实是程序通过 getcontext 先保存了一个上下文,然后输出“Hello world”,在通过 setcontext 恢复到 getcontext 的地方,重新执行代码,所以导致程序不断的输出“Hello world”,在我这个菜鸟的眼里,这简直就是一个神奇的跳转。那么问题来了, ucontext 到底是什么?

这里笔者不多做介绍了,推荐一篇文章,讲的比较详细: ucontext-人人都可以实现的简单协程库 这里我们只需要知道,所谓 coobjc 介绍文章中提到的使用汇编语言模拟 ucontext ,其实就是模拟的上面例子中的 setcontextgetcontext 等函数。为了证明笔者的猜想,笔者打开了 coobjc 源码库,发现里面的唯一的汇编文件 coroutine_context.s

阿里开源 iOS 协程开发框架 coobjc源码分析

查看该文件,发现了这么几个函数:

  • _coroutine_getcontext
  • _coroutine_begin
  • _coroutine_setcontext

果然验证了笔者的想法。这三个方法被暴露在文件 coroutine_context.h 中,供后序调用:

extern int coroutine_getcontext (coroutine_ucontext_t *__ucp);
extern int coroutine_setcontext (coroutine_ucontext_t *__ucp);
extern int coroutine_begin (coroutine_ucontext_t *__ucp);
复制代码

这么一来,我们之前的程序可以改写成如下:

#import <coobjc/coroutine_context.h>

int main(int argc, const char *argv[]) {
    coroutine_ucontext_t context;
    coroutine_getcontext(&context);
    puts("Hello world");
    sleep(1);
    coroutine_setcontext(&context);
    return 0;
}
复制代码

返回的结果仍然不变,一直打印“hello world”。

深入协程

上面我们只简单的介绍了 coobjc ,也了解到 coobjc 基本都是参考了 ucontext 。那下面的例子中,笔者尽可能先介绍 ucontext ,然后再应用到 coobjc 对应的方法中。 我们继续讨论上文提到的几个函数,并说明一下其作用:

int  getcontext(ucontext_t *uctp)
复制代码

这个方法是,获取当前上下文,并将上下文设置到 uctp 中, uctp 是个上下文结构体,其定义如下:

_STRUCT_UCONTEXT
{
	int                     uc_onstack;
	__darwin_sigset_t       uc_sigmask;     /* signal mask used by this context */
	_STRUCT_SIGALTSTACK     uc_stack;       /* stack used by this context */
	_STRUCT_UCONTEXT        *uc_link;       /* pointer to resuming context */
	__darwin_size_t	        uc_mcsize;      /* size of the machine context passed in */
	_STRUCT_MCONTEXT        *uc_mcontext;   /* pointer to machine specific context */
#ifdef _XOPEN_SOURCE
	_STRUCT_MCONTEXT        __mcontext_data;
#endif /* _XOPEN_SOURCE */
};

/* user context */
typedef _STRUCT_UCONTEXT	ucontext_t;     /* [???] user context */	
复制代码

以上是 ucontext 的数据结构,其内部的几个属性介绍一下: 当当前上下文(如使用makecontext创建的上下文)运行终止时系统会恢复 uc_link 指向的上下文; uc_sigmask 为该上下文中的阻塞信号集合; uc_stack 为该上下文中使用的栈; uc_mcontext 保存的上下文的特定机器表示,包括调用线程的特定寄存器等。其实还蛮好理解的, ucontext 其实就存放一些必要的数据,这些数据还包括拯救成功或者失败的情况需要的数据。

接下来说另外一个函数

int  setcontext(const ucontext_t *cut)
复制代码

该函数是设置当前的上下文为 cutsetcontext 的上下文 cut 应该通过 getcontext 或者 makecontext 取得,如果调用成功则不返回。如果上下文是通过调用 getcontext() 取得,程序会继续执行这个调用。如果上下文是通过调用 makecontext 取得,程序会调用 makecontext 函数的第二个参数指向的函数,如果 func 函数返回,则恢复 makecontext 第一个参数指向的上下文第一个参数指向的上下文 context_t 中指向的 uc_link .如果 uc_link 为NULL,则线程退出。

同样的,我们画个表类比一下 ucontextcoobjc 的函数:

ucontext coobjc 含义
setcontext coroutine_setcontext 设置协程上下文
getcontext coroutine_getcontext 获取协程上下文
makecontext coroutine_create 创建一个协程上下文

以及 ucontext 以及 coobjc 结构体定义:


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

智能时代

智能时代

吴军 / 中信出版集团 / 2016-8 / 68.00

大数据和机器智能的出现,对我们的技术发展、商业和社会都会产生重大的影响。作者吴军在《智能时代:大数据与智能革命重新定义未来》中指出,首先,我们在过去认为非常难以解决的问题,会因为大数据和机器智能的使用而迎刃而解,比如解决癌症个性化治疗的难题。同时,大数据和机器智能还会彻底改变未来的商业模式,很多传统的行业都将采用智能技术实现升级换代,同时改变原有的商业模式。大数据和机器智能对于未来社会的影响是全方......一起来看看 《智能时代》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具