coobjc- iOS 协程开发框架

网友投稿 2956 2022-10-10

coobjc- iOS 协程开发框架

coobjc- iOS 协程开发框架

coobjc 为 Objective-C 和 Swift 提供了协程功能。coobjc 支持 await、generator 和 actormodel,接口参考了 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://kotlincn-/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 文档

阅读 协程框架设计 文档。阅读 coobjc Objective-C Guide 文档。阅读 coobjc Swift Guide 文档。阅读 cokit framework 文档, 学习如何使用系统接口封装的 api 。

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)); }});//主线程更新UIco(^{ 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*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 返回值中获取多个值。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:git的三个区域比较
下一篇:DataFrame(10):DataFrame运算——累计统计函数
相关文章

 发表评论

暂时没有评论,来抢沙发吧~