coobjc 正在参加 2020 年度 OSC 中国开源项目评选,请投票支持!
coobjc 在 2020 年度 OSC 中国开源项目评选 中已获得 {{ projectVoteCount }} 票,请投票支持!
投票让它出道
已投票
coobjc 获得 2020 年度 OSC 中国开源项目评选「最佳人气项目」 !
coobjc 获得 2020 年度 OSC 中国开源项目评选「最佳人气项目」「最积极运营项目」 !
coobjc 获得 2020 年度 OSC 中国开源项目评选「最积极运营项目」 !

软件简介

coobjc 为 Objective-C 和 Swift 提供了协程功能。coobjc 支持 await、generator 和 actor model,接口参考了 C# 、Javascript 和 Kotlin 中的很多设计。我们还提供了 cokit 库为 Foundation 和 UIKit 中的部分 API 提供了协程化支持,包括 NSFileManager、JSON、NSData 与 UIImage 等。coobjc 也提供了元组的支持。

0x0 iOS 异步编程问题

基于 Block 的异步编程回调是目前 iOS 使用最广泛的异步编程方式,iOS 系统提供的 GCD 库让异步开发变得很简单方便,但是基于这种编程方式的缺点也有很多,主要有以下几点:

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

上述问题反应到线上应用本身就会出现大量的多线程崩溃。

0x1 解决方案

上述问题在很多系统和语言中都会遇到,解决问题的标准方式就是使用协程。这里不介绍太多的理论,简单说协程就是对基础函数的扩展,可以让函数异步执行的时候挂起然后返回值。协程可以用来实现 generator ,异步模型以及其他强大的能力。

Kotlin 是这两年由 JetBrains 推出的支持现代多平台应用的静态编程语言,支持 JVM ,Javascript ,目前也可以在 iOS 上执行,这两年在开发者社区中也是比较火。

在 Kotlin 语言中基于协程的 async/await ,generator/yield 等异步化技术都已经成了语法标配,Kotlin 协程相关的介绍,大家可以参考:https://www.kotlincn.net/docs/reference/coroutines/basics.html

0x2 协程

协程是一种在非抢占式多任务场景下生成可以在特定位置挂起和恢复执行入口的程序组件

协程的概念在60年代就已经提出,目前在服务端中应用比较广泛,在高并发场景下使用极其合适,可以极大降低单机的线程数,提升单机的连接和处理能力,但是在移动研发中,iOS和android目前都不支持协程的使用

0x3 coobjc 框架

coobjc 是由手机淘宝架构团队推出的能在 iOS 上使用的协程开发框架,目前支持 Objective-C 和 Swift 中使用,我们底层使用汇编和 C 语言进行开发,上层进行提供了 Objective-C 和 Swift 的接口,目前以 Apache 开源协议进行了开源。

0x31 安装

  • cocoapods 安装:  pod 'coobjc'
  • 源码安装: 所有代码在 ./coobjc 目录下

0x32 文档

0x33 特性

async/await

  • 创建协程

使用 co_launch 方法创建协程

co_launch(^{
    ...
});

co_launch 创建的协程默认在当前线程进行调度

  • await 异步方法

在协程中我们使用 await 方法等待异步方法执行结束,得到异步执行结果

- (void)viewDidLoad{
    ...
		co_launch(^{
    		NSData *data = await(downloadDataFromUrl(url));
    		UIImage *image = await(imageFromData(data));
    		self.imageView.image = image;
		});
}

上述代码将原本需要 dispatch_async 两次的代码变成了顺序执行,代码更加简洁

  • 错误处理

在协程中,我们所有的方法都是直接返回值的,并没有返回错误,我们在执行过程中的错误是通过 co_getError() 获取的,比如我们有以下从网络获取数据的接口,在失败的时候, promise 会 reject:error

- (CCOPromise*)co_GET:(NSString*)url
  parameters:(NSDictionary*)parameters{
    CCOPromise *promise = [CCOPromise promise];
    [self GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        [promise fulfill:responseObject];
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        [promise reject:error];
    }];
    return promise;
}

那我们在协程中可以如下使用:

co_launch(^{
    id response = await([self co_GET:feedModel.feedUrl parameters:nil]);
    if(co_getError()){
        //处理错误信息
    }
    ...
});

生成器

  • 创建生成器

我们使用 co_sequence 创建生成器

COCoroutine *co1 = co_sequence(^{
            int index = 0;
            while(co_isActive()){
                yield_val(@(index));
                index++;
            }
        });

在其他协程中,我们可以调用 next 方法,获取生成器中的数据

co_launch(^{
            for(int i = 0; i < 10; i++){
                val = [[co1 next] intValue];
            }
        });
  • 使用场景

生成器可以在很多场景中进行使用,比如消息队列、批量下载文件、批量加载缓存等:

int unreadMessageCount = 10;
NSString *userId = @"xxx";
COSequence *messageSequence = sequenceOnBackgroundQueue(@"message_queue", ^{
   //在后台线程执行
    while(1){
        yield(queryOneNewMessageForUserWithId(userId));
    }
});

//主线程更新UI
co(^{
   for(int i = 0; i < unreadMessageCount; i++){
       if(!isQuitCurrentView()){
           displayMessage([messageSequence take]);
       }
   }
});

通过生成器,我们可以把传统的生产者加载数据->通知消费者模式,变成消费者需要数据->告诉生产者加载模式,避免了在多线程计算中,需要使用很多共享变量进行状态同步,消除了在某些场景下对于锁的使用

Actor

_ Actor 的概念来自于 Erlang ,在 AKKA 中,可以认为一个 Actor 就是一个容器,用以存储状态、行为、Mailbox 以及子 Actor 与 Supervisor 策略。Actor 之间并不直接通信,而是通过 Mail 来互通有无。_

  • 创建 actor

我们可以使用 co_actor_onqueue 在指定线程创建 actor

CCOActor *actor = co_actor_onqueue(^(CCOActorChan *channel) {
    ...  //定义 actor 的状态变量
    for(CCOActorMessage *message in channel){
        ...//处理消息
    }
}, q);
  • 给 actor 发送消息

actor 的 send 方法可以给 actor 发送消息

CCOActor *actor = co_actor_onqueue(^(CCOActorChan *channel) {
    ...  //定义actor的状态变量
    for(CCOActorMessage *message in channel){
        ...//处理消息
    }
}, q);

// 给actor发送消息
[actor send:@"sadf"];
[actor send:@(1)];

元组

  • 创建元组

使用 co_tuple 方法来创建元组

COTuple *tup = co_tuple(nil, @10, @"abc");
NSAssert(tup[0] == nil, @"tup[0] is wrong");
NSAssert([tup[1] intValue] == 10, @"tup[1] is wrong");
NSAssert([tup[2] isEqualToString:@"abc"], @"tup[2] is wrong");

可以在元组中存储任何数据

  • 元组取值

可以使用 co_unpack 方法从元组中取值

id val0;
NSNumber *number = nil;
NSString *str = nil;
co_unpack(&val0, &number, &str) = co_tuple(nil, @10, @"abc");
NSAssert(val0 == nil, @"val0 is wrong");
NSAssert([number intValue] == 10, @"number is wrong");
NSAssert([str isEqualToString:@"abc"], @"str is wrong");

co_unpack(&val0, &number, &str) = co_tuple(nil, @10, @"abc", @10, @"abc");
NSAssert(val0 == nil, @"val0 is wrong");
NSAssert([number intValue] == 10, @"number is wrong");
NSAssert([str isEqualToString:@"abc"], @"str is wrong");

co_unpack(&val0, &number, &str, &number, &str) = co_tuple(nil, @10, @"abc");
NSAssert(val0 == nil, @"val0 is wrong");
NSAssert([number intValue] == 10, @"number is wrong");
NSAssert([str isEqualToString:@"abc"], @"str is wrong");

NSString *str1;

co_unpack(nil, nil, &str1) = co_tuple(nil, @10, @"abc");
NSAssert([str1 isEqualToString:@"abc"], @"str1 is wrong");
  • 在协程中使用元组

首先创建一个 promise 来处理元组里的值

COPromise<COTuple*>*
cotest_loadContentFromFile(NSString *filePath){
    return [COPromise promise:^(COPromiseFullfill  _Nonnull resolve, COPromiseReject  _Nonnull reject) {
        if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
            NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
            resolve(co_tuple(filePath, data, nil));
        }
        else{
            NSError *error = [NSError errorWithDomain:@"fileNotFound" code:-1 userInfo:nil];
            resolve(co_tuple(filePath, nil, error));
        }
    }];
}

然后,你可以像下面这样获取元组里的值:

co_launch(^{
    NSString *tmpFilePath = nil;
    NSData *data = nil;
    NSError *error = nil;
    co_unpack(&tmpFilePath, &data, &error) = await(cotest_loadContentFromFile(filePath));
    XCTAssert([tmpFilePath isEqualToString:filePath], @"file path is wrong");
    XCTAssert(data.length > 0, @"data is wrong");
    XCTAssert(error == nil, @"error is wrong");
});

使用元组你可以从 await 返回值中获取多个值。

展开阅读全文

代码

的 Gitee 指数为
超过 的项目

评论 (1)

加载中
您好,java有协程吗?x协程和线程的区别是什么?
2019/03/04 10:44
回复
举报
更多评论
暂无内容
2020/03/08 22:01

淘宝开源iOS协程开发框架 coobjc!

导读:刚刚,阿里淘宝正式对外开源了基于Apache 2.0协议协程开发框架coobjc,开发者们可以在Github上自主下载。 coobjc是为iOS平台打造的开源协程开发框架,支持Objective-C和Swift,同时提供了cokit库为Foundation和UIKit中的部分API提供了协程化支持,本文将为大家详细介绍coobjc的设计理念及核心优势。 开源地址 https://github.com/alibaba/coobjc iOS异步编程问题 从2008年第一个iOS版本发布至今的11年时间里,iOS的异步编...

0
0
发表了博客
2019/03/01 13:09

刚刚,阿里开源 iOS 协程开发框架 coobjc!

阿里妹导读:刚刚,阿里巴巴正式对外开源了基于 Apache 2.0 协议的协程开发框架 coobjc,开发者们可以在 Github 上自主下载。 coobjc是为iOS平台打造的开源协程开发框架,支持Objective-C和Swift,同时提供了cokit库为Foundation和UIKit中的部分API提供了协程化支持,本文将为大家详细介绍coobjc的设计理念及核心优势。 开源地址 https://github.com/alibaba/coobjc iOS异步编程问题 从2008年第一个iOS版本发布至今的11年时间里...

0
0
发表了博客
2019/03/05 18:32

阿里开源 iOS 协程开发框架 coobjc!--异步编程的问题与解决方案

阿里妹导读:刚刚,阿里巴巴正式对外开源了基于 Apache 2.0 协议的协程开发框架 coobjc,开发者们可以在 Github 上自主下载。 coobjc是为iOS平台打造的开源协程开发框架,支持Objective-C和Swift,同时提供了cokit库为Foundation和UIKit中的部分API提供了协程化支持,本文将为大家详细介绍coobjc的设计理念及核心优势。 开源地址 https://github.com/alibaba/coobjc iOS异步编程问题 从2008年第一个iOS版本发布至今的11年时间里...

0
0
发表于大前端专区
2019/03/08 09:04

D2 日报 2019年 03月 08日

![banner](https://qiniucdn.fairyever.com/20190305212058.png) ## 新闻 * ➡️ **Introducing draft pull requests** `非中文` GitHub 新增 Draft Pull Request 协作流程 [github.blog](https://github.blog/2019-02-14-introducing-draft-pull-requests/) ## 开源项目 * ➡️ **rocky/python-uncompyle6** `非中文` `watch 39` `star 1022` `fork 127` Python 反编译工具,可以将 Python 字节码 pyc 文件反编译回源码 py 文件...

0
0
2020/05/25 22:49

基于协程的编程方式在移动端研发的思考及最佳实践

本文来自阿里巴巴 无线开发专家 彭玉堂(巴格)在 QCon 2019 全球软件开发大会的分享,今天小编带大家一起来深入了解一下。 1. 超级App的性能和代码痛点 在 iOS 开发中线程使用特别方便,但是多线程使用不当引发的崩溃问题很多。 多线程访问引发野指针问题 多线程访问引发容器类崩溃问题 多线程访问引发过渡释放问题 以手机淘宝为例,整个生命周期大量使用线程,多线程使用不当引发的崩溃问题占比达到了60%以上。 为了解决多线程...

0
0
2020/07/30 16:20

阿里淘系优质开源项目推荐

开源推动着互联网技术的发展。多年来,阿里巴巴淘系技术一直积极拥抱开源事业,无论是开源软件的应用、回馈以至自研技术的开源都非常活跃,近两年我们更是开源了MNN、飞冰ICE、3D-FUTURE & 3D-FRONT 等项目,在开源社区中,也获得了广泛开发者的支持和使用。 为了让开发者们更好的应用到自己的项目中,橙子对淘系新兴和经典开源项目做了一次汇总,另附视频讲解及文章介绍,以供大家学习参考。(文末有惊喜!!!) 项目一 项目名...

0
0
发表了博客
2019/03/04 22:40

async/await actor promise 异步编程

Python协程:从yield/send到async/await http://blog.guoyb.com/2016/07/03/python-coroutine/ Async/Await替代Promise的6个理由 https://www.cnblogs.com/fundebug/p/6667725.html 重构:从Promise到Async/Await https://blog.fundebug.com/2017/12/13/reconstruct-from-promise-to-async-await/ 理解Python协程:从yield/send到yield from再到async/await https://blog.csdn.net/soonfly/article/details/78361819 Swift 并行编...

0
0
2020/04/15 22:20

淘系技术大事件回顾 | 我们一起走过的2019

2019年初豪言壮志立下的 flag 都实现了吗? 写多少行代码?get了几项新技能?踩过多少坑? 今天淘小橙带大家一起回顾下「淘系技术2019」 (文末有干货锦集,不容错过!) 2019年,淘小橙共分享了146篇文章 年底再宠粉一波,年度好文都给大家整理好啦!请查收 最贵篇:淘系技术,实力为2019年双11而战!稳! 海外篇:淘系技术嘉年华|新零售,技数创造新商业 尝鲜篇:我们把淘宝“黑了”?内测名额限量开放! 解压篇:双12最潮围巾...

0
0
发表于大前端专区
2020/04/20 14:49

高德APP启动耗时剖析与优化实践(iOS篇)

前言 最近高德地图APP完成了一次启动优化专项,超预期将双端启动的耗时都降低了65%以上,iOS在iPhone7上速度达到了400毫秒以内。就像产品们用后说的,快到不习惯。算一下每天为用户省下的时间,还是蛮有成就感的,本文做个小结。 (文中配图均为多才多艺的技术哥哥手绘) 启动阶段性能多维度分析 要优化,首先要做到的是对启动阶段的各个性能纬度做分析,包括主线程耗时、CPU、内存、I/O、网络。这样才能更加全面的掌握启动阶段...

0
0
没有更多内容
加载失败,请刷新页面
点击加载更多
加载中
下一页
暂无内容
1 评论
23 收藏
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部