coobjc为Objective-C和Swift提供了协程功能。coobjc支持await、generator和actormodel,接口参考了C#、Javascript和Kotlin中的很多设计。我们还提供了 cokit库为Foundation和UIKit中的部分API提供了协程化支持,包括NSFileManager、JSON、NSData与UIImage等。coobjc也提供了元组的支持。
0x0iOS异步编程问题基于Block的异步编程回调是目前iOS使用最广泛的异步编程方式,iOS系统提供的GCD库让异步开发变得很简单方便,但是基于这种编程方式的缺点也有很多,主要有以下几点:
容易进入"嵌套地狱"错误处理复杂和冗长容易忘记调用completionhandler条件执行变得很困难从互相独立的调用中组合返回结果变得极其困难在错误的线程中继续执行难以定位原因的多线程崩溃锁和信号量滥用带来的卡顿、卡死上述问题反应到线上应用本身就会出现大量的多线程崩溃。
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目前都不支持协程的使用
0x3coobjc框架coobjc是由手机淘宝架构团队推出的能在iOS上使用的协程开发框架,目前支持Objective-C和Swift中使用,我们底层使用汇编和C语言进行开发,上层进行提供了Objective-C和Swift的接口,目前以Apache开源协议进行了开源。
0x31安装cocoapods安装: pod'coobjc'源码安装:所有代码在./coobjc目录下0x32文档阅读 协程框架设计 文档。阅读 coobjcObjective-CGuide 文档。阅读 coobjcSwiftGuide 文档。阅读 cokitframework 文档,学习如何使用系统接口封装的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*)urlparameters:(NSDictionary*)parameters{CCOPromise*promise=[CCOPromisepromise];[selfGET:urlparameters:parametersprogress:nilsuccess:^(NSURLSessionDataTask*_Nonnulltask,id_NullableresponseObject){[promisefulfill:responseObject];}failure:^(NSURLSessionDataTask*_Nullabletask,NSError*_Nonnullerror){[promisereject:error];}];returnpromise;}那我们在协程中可以如下使用:
co_launch(^{idresponse=await([selfco_GET:feedModel.feedUrlparameters:nil]);if(co_getError()){//处理错误信息}...});生成器创建生成器我们使用 co_sequence 创建生成器
COCoroutine*co1=co_sequence(^{intindex=0;while(co_isActive()){yield_val(@(index));index++;}});在其他协程中,我们可以调用 next 方法,获取生成器中的数据
co_launch(^{for(inti=0;i<10;i++){val=[[co1next]intValue];}});使用场景生成器可以在很多场景中进行使用,比如消息队列、批量下载文件、批量加载缓存等:
intunreadMessageCount=10;NSString*userId=@"xxx";COSequence*messageSequence=sequenceOnBackgroundQueue(@"message_queue",^{//在后台线程执行while(1){yield(queryOneNewMessageForUserWithId(userId));}});//主线程更新UIco(^{for(inti=0;i<unreadMessageCount;i++){if(!isQuitCurrentView()){displayMessage([messageSequencetake]);}}});通过生成器,我们可以把传统的生产者加载数据->通知消费者模式,变成消费者需要数据->告诉生产者加载模式,避免了在多线程计算中,需要使用很多共享变量进行状态同步,消除了在某些场景下对于锁的使用
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*messageinchannel){...//处理消息}},q);给actor发送消息actor的 send 方法可以给actor发送消息
CCOActor*actor=co_actor_onqueue(^(CCOActorChan*channel){...//定义actor的状态变量for(CCOActorMessage*messageinchannel){...//处理消息}},q);//给actor发送消息[actorsend:@"sadf"];[actorsend:@(1)];元组创建元组使用 co_tuple 方法来创建元组
COTuple*tup=co_tuple(nil,@10,@"abc");NSAssert(tup[0]==nil,@"tup[0]iswrong");NSAssert([tup[1]intValue]==10,@"tup[1]iswrong");NSAssert([tup[2]isEqualToString:@"abc"],@"tup[2]iswrong");可以在元组中存储任何数据
元组取值可以使用 co_unpack 方法从元组中取值
idval0;NSNumber*number=nil;NSString*str=nil;co_unpack(&val0,&number,&str)=co_tuple(nil,@10,@"abc");NSAssert(val0==nil,@"val0iswrong");NSAssert([numberintValue]==10,@"numberiswrong");NSAssert([strisEqualToString:@"abc"],@"striswrong");co_unpack(&val0,&number,&str)=co_tuple(nil,@10,@"abc",@10,@"abc");NSAssert(val0==nil,@"val0iswrong");NSAssert([numberintValue]==10,@"numberiswrong");NSAssert([strisEqualToString:@"abc"],@"striswrong");co_unpack(&val0,&number,&str,&number,&str)=co_tuple(nil,@10,@"abc");NSAssert(val0==nil,@"val0iswrong");NSAssert([numberintValue]==10,@"numberiswrong");NSAssert([strisEqualToString:@"abc"],@"striswrong");NSString*str1;co_unpack(nil,nil,&str1)=co_tuple(nil,@10,@"abc");NSAssert([str1isEqualToString:@"abc"],@"str1iswrong");在协程中使用元组首先创建一个promise来处理元组里的值
COPromise<COTuple*>*cotest_loadContentFromFile(NSString*filePath){return[COPromisepromise:^(COPromiseFullfill_Nonnullresolve,COPromiseReject_Nonnullreject){if([[NSFileManagerdefaultManager]fileExistsAtPath:filePath]){NSData*data=[[NSDataalloc]initWithContentsOfFile:filePath];resolve(co_tuple(filePath,data,nil));}else{NSError*error=[NSErrorerrorWithDomain:@"fileNotFound"code:-1userInfo: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([tmpFilePathisEqualToString:filePath],@"filepathiswrong");XCTAssert(data.length>0,@"dataiswrong");XCTAssert(error==nil,@"erroriswrong");});使用元组你可以从 await 返回值中获取多个值。
评论