阿里妹导读:刚刚,阿里巴巴正式对外开源了基于 Apache 2.0 协议的协程开发框架 coobjc,开发者们可以在 Github 上自主下载。
“专业、务实、高效、创新、把客户的事当成自己的事”是我们每一个人一直以来坚持追求的企业文化。 创新互联是您可以信赖的网站建设服务商、专业的互联网服务提供商! 专注于成都网站设计、成都做网站、外贸网站建设、软件开发、设计服务业务。我们始终坚持以客户需求为导向,结合用户体验与视觉传达,提供有针对性的项目解决方案,提供专业性的建议,创新互联建站将不断地超越自我,追逐市场,引领市场!
coobjc是为iOS平台打造的开源协程开发框架,支持Objective-C和Swift,同时提供了cokit库为Foundation和UIKit中的部分API提供了 协程 化支持,本文将为大家详细介绍coobjc的设计理念及核心优势。
从2008年第一个iOS版本发布至今的11年时间里,iOS的异步编程方式发展缓慢。
基于 Block 的异步编程回调是目前 iOS 使用最广泛的异步编程方式,iOS 系统提供的 GCD 库让异步开发变得很简单方便,但是基于这种编程方式的缺点也有很多,主要有以下几点:
针对多线程以及尤其引发的各种崩溃和性能问题,我们制定了很多编程规范、进行了各种新人培训,尝试降低问题发生的概率,但是问题依然很严峻,多线程引发的问题占比并没有明显的下降,异步编程本来就是很复杂的事情,单靠规范和培训是难以从根本上解决问题的,需要有更加好的编程方式来解决。
上述问题在很多系统和语言开发中都可能会碰到,解决问题的标准方式就是使用协程,C#、Kotlin、Python、Javascript 等热门语言均支持协程极其相关语法,使用这些语言的开发者可以很方便的使用协程及相关功能进行异步编程。
2017 年的 C++ 标准开始支持协程,Swift5 中也包含了协程相关的标准,从现在的发展趋势看基于协程的全新的异步编程方式,是我们解决现有异步编程问题的有效的方式,但是苹果基本已经不会升级 Objective-C 了,因此使用Objective-C的开发者是无法使用官方的协程能力的,而最新 Swift 的发布和推广也还需要时日,为了让广大iOS开发者能快速享受到协程带来的编程方式上的改变,手机淘宝架构团队基于长期对系统底层库和汇编的研究,通过汇编和C语言实现了支持 Objective-C 和 Swift 协程的完美解决方案 —— coobjc。
核心能力
内置系统扩展库
coobjc设计
最底层是协程内核,包含了栈切换的管理、协程调度器的实现、协程间通信channel的实现等。
中间层是基于协程的操作符的包装,目前支持async/await、Generator、Actor等编程模型。
最上层是对系统库的协程化扩展,目前基本上覆盖了Foundation和UIKit的所有IO和耗时方法。
核心实现原理
协程的核心思想是控制调用栈的主动让出和恢复。一般的协程实现都会提供两个重要的操作:
我们基于线程的代码执行时候,是没法做出暂停操作的,我们现在要做的事情就是要代码执行能够暂停,还能够再恢复。 基本上代码执行都是一种基于调用栈的模型,所以如果我们能把当前调用栈上的状态都保存下来,然后再能从缓存中恢复,那我们就能够实现yield和 resume。
实现这样操作有几种方法呢?
上述第三种和第四种只是能过做到跳转,但是没法保存调用栈上的状态,看起来基本上不能算是实现了协程,只能算做做demo,第五种除非官方支持,否则自行改写编译器通用性很差。而第一种方案的 ucontext 在iOS上是废弃了的,不能使用。那么我们使用的是第二种方案,自己用汇编模拟一下 ucontext。
模拟ucontext的核心是通过getContext和setContext实现保存和恢复调用栈。需要熟悉不同CPU架构下的调用约定(Calling Convention). 汇编实现就是要针对不同cpu实现一套,我们目前实现了 armv7、arm64、i386、x86_64,支持iPhone真机和模拟器。
说了这么多,还是看看代码吧,我们从一个简单的网络请求加载图片功能来看看coobjc到底是如何使用的。
下面是最普通的网络请求的写法:
下面是使用coobjc库协程化改造后的代码:
原本需要20行的代码,通过coobjc协程化改造后,减少了一半,整个代码逻辑和可读性都更加好,这就是coobjc强大的能力,能把原本很复杂的异步代码,通过协程化改造,转变成逻辑简洁的顺序调用。
coobjc还有很多其他强大的能力,本文对于coobjc的实际使用就不过多介绍了,感兴趣的朋友可以去官方github仓库自行下载查看。
我们在iPhone7 iOS11.4.1的设备上使用协程和传统多线程方式分别模拟高并发读取数据的场景,下面是两种方式得到的压测数据。
从上面的表格我们可以看到使用在并发量很小的场景,由于多线程可以完全使用设备的计算核心,因此coobjc总耗时要比传统多线程略高,但是由于整体耗时都很小,因此差异并不明显,但是随着并发量的增大,coobjc的优势开始逐渐体现出来,当并发量超过1000以后,传统多线程开始出现线程分配异常,而导致很多并发任务并没有执行,因此在上表中显示的是大于20秒,实际是任务已经无法正常执行了,但是coobjc仍然可以正常运行。
我们在手机淘宝这种超级App中尝试了协程化改造,针对部分性能差的页面,我们发现在滑动过程中存在很多主线程IO调用、数据解析,导致帧率下降严重,通过引入coobjc,在不改变原有业务代码的基础上,通过全局hook部分IO、数据解析方法,即可让原来在主线程中同步执行的IO方法异步执行,并且不影响原有的业务逻辑,通过测试验证,这样的改造在低端机(iPhone6及以下的机器)上的帧率有20%左右的提升。
简明
易用
清晰
性能
程序是写来给人读的,只会偶尔让机器执行一下。——Abelson and Sussman
基于协程实现的编程范式能够帮助开发者编写出更加优美、健壮、可读性更强的代码。
协程可以帮助我们在编写并发代码的过程中减少线程和锁的使用,提升应用的性能和稳定性。
本文作者:淘宝技术
sstoolkit 一套Category类型的库,附带很多自定义控件,功能不错~
BlocksKit 将Block风格带入UIKit和Founcation
cocoa-helpers 一些Cocoa的扩展 2年前的工程
CoconutKit 一系列扩展和一些自定组件
STUtils 一系列扩展包
PSFoundation 一系列扩展 和功能增强
ConciseKit 一系列宏定义 挺有意思
DTFoundation 又一系列扩展
BBBootstrap 一些常见功能的扩展库
上一篇我们说到组件化开发是一个App最大一层的架构,那具体到了一个一个的业务组件里,比组件化开发小一点的架构就是三层架构。所谓三层架构就是指把项目中所有的类划分到不同的层,目的是使项目逻辑更加清晰,提升开发和维护的效率,我们一般会把项目分为三层:数据层、业务层、视图层。
注意:严格来讲,三层架构并不真正就比组件化开发小,它们俩只是从不同的角度去做架构而已,往往是相互融合的,比如组件化开发里网络库就是三层架构里数据层的内容,UI库就是三层架构里视图层里的内容等,这里之所以说“三层架构是比组件化开发小一点的架构,主要是针对业务组件来说的”,当然甚至我们在不使用组件化开发时项目里依然可以使用三层架构。
比三层架构再小一点的架构就是视图层架构,我们常说的MVC、MVVM都是˛视图层的架构。
MVC,即Model-View-Controller,它们的关系如下:
MVVM,即Model-View-ViewModel,它们的关系如下:
原文地址:
一、引言
iOS10系统是一个较有突破性的系统,其在Message,Notification等方面都开放了很多实用性的开发接口。本篇博客将主要探讨iOS10中新引入的SpeechFramework框架。有个这个框架,开发者可以十分容易的为自己的App添加语音识别功能,不需要再依赖于其他第三方的语音识别服务,并且,Apple的Siri应用的强大也证明了Apple的语音服务是足够强大的,不通过第三方,也大大增强了用户的安全性。
二、SpeechFramework框架中的重要类
SpeechFramework框架比较轻量级,其中的类并不十分冗杂,在学习SpeechFramework框架前,我们需要对其中类与类与类之间的关系有个大致的熟悉了解。
SFSpeechRecognizer:这个类是语音识别的操作类,用于语音识别用户权限的申请,语言环境的设置,语音模式的设置以及向Apple服务发送语音识别的请求。
SFSpeechRecognitionTask:这个类是语音识别服务请求任务类,每一个语音识别请求都可以抽象为一个SFSpeechRecognitionTask实例,其中SFSpeechRecognitionTaskDelegate协议中约定了许多请求任务过程中的监听方法。
SFSpeechRecognitionRequest:语音识别请求类,需要通过其子类来进行实例化。
SFSpeechURLRecognitionRequest:通过音频URL来创建语音识别请求。
SFSpeechAudioBufferRecognitionRequest:通过音频流来创建语音识别请求。
SFSpeechRecognitionResult:语音识别请求结果类。
SFTranscription:语音转换后的信息类。
SFTranscriptionSegment:语音转换中的音频节点类。
三、申请用户语音识别权限与进行语音识别请求
开发者若要在自己的App中使用语音识别功能,需要获取用户的同意。首先需要在工程的Info.plist文件中添加一个Privacy-Speech Recognition Usage Description键,其实需要对应一个String类型的值,这个值将会在系统获取权限的警告框中显示,Info.plist文件如下图所示:
使用SFSpeechRecognize类的requestAuthorization方法来进行用户权限的申请,用户的反馈结果会在这个方法的回调block中传入,如下:
//申请用户语音识别权限
[SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
}];
SFSpeechRecognizerAuthorzationStatus枚举中定义了用户的反馈结果,如下:
typedef NS_ENUM(NSInteger, SFSpeechRecognizerAuthorizationStatus) {
//结果未知 用户尚未进行选择
SFSpeechRecognizerAuthorizationStatusNotDetermined,
//用户拒绝授权语音识别
SFSpeechRecognizerAuthorizationStatusDenied,
//设备不支持语音识别功能
SFSpeechRecognizerAuthorizationStatusRestricted,
//用户授权语音识别
SFSpeechRecognizerAuthorizationStatusAuthorized,
};
如果申请用户语音识别权限成功,开发者可以通过SFSpeechRecognizer操作类来进行语音识别请求,示例如下:
//创建语音识别操作类对象
SFSpeechRecognizer * rec = [[SFSpeechRecognizer alloc]init];
//通过一个音频路径创建音频识别请求
SFSpeechRecognitionRequest * request = [[SFSpeechURLRecognitionRequest alloc]initWithURL:[[NSBundle mainBundle] URLForResource:@"7011" withExtension:@"m4a"]];
//进行请求
[rec recognitionTaskWithRequest:request resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
//打印语音识别的结果字符串
NSLog(@"%@",result.bestTranscription.formattedString);
}];
四、深入SFSpeechRecognizer类
SFSpeechRecognizer类的主要作用是申请权限,配置参数与进行语音识别请求。其中比较重要的属性与方法如下:
//获取当前用户权限状态
+ (SFSpeechRecognizerAuthorizationStatus)authorizationStatus;
//申请语音识别用户权限
+ (void)requestAuthorization:(void(^)(SFSpeechRecognizerAuthorizationStatus status))handler;
//获取所支持的所有语言环境
+ (NSSetNSLocale * *)supportedLocales;
//初始化方法 需要注意 这个初始化方法将默认以设备当前的语言环境作为语音识别的语言环境
- (nullable instancetype)init;
//初始化方法 设置一个特定的语言环境
- (nullable instancetype)initWithLocale:(NSLocale *)locale NS_DESIGNATED_INITIALIZER;
//语音识别是否可用
@property (nonatomic, readonly, getter=isAvailable) BOOL available;
//语音识别操作类协议代理
@property (nonatomic, weak) idSFSpeechRecognizerDelegate delegate;
//设置语音识别的配置参数 需要注意 在每个语音识别请求中也有这样一个属性 这里设置将作为默认值
//如果SFSpeechRecognitionRequest对象中也进行了设置 则会覆盖这里的值
/*
typedef NS_ENUM(NSInteger, SFSpeechRecognitionTaskHint) {
SFSpeechRecognitionTaskHintUnspecified = 0, // 无定义
SFSpeechRecognitionTaskHintDictation = 1, // 正常的听写风格
SFSpeechRecognitionTaskHintSearch = 2, // 搜索风格
SFSpeechRecognitionTaskHintConfirmation = 3, // 短语风格
};
*/
@property (nonatomic) SFSpeechRecognitionTaskHint defaultTaskHint;
//使用回调Block的方式进行语音识别请求 请求结果会在Block中传入
- (SFSpeechRecognitionTask *)recognitionTaskWithRequest:(SFSpeechRecognitionRequest *)request
resultHandler:(void (^)(SFSpeechRecognitionResult * __nullable result, NSError * __nullable error))resultHandler;
//使用代理回调的方式进行语音识别请求
- (SFSpeechRecognitionTask *)recognitionTaskWithRequest:(SFSpeechRecognitionRequest *)request
delegate:(id SFSpeechRecognitionTaskDelegate)delegate;
//设置请求所占用的任务队列
@property (nonatomic, strong) NSOperationQueue *queue;
SFSpeechRecognizerDelegate协议中只约定了一个方法,如下:
//当语音识别操作可用性发生改变时会被调用
- (void)speechRecognizer:(SFSpeechRecognizer *)speechRecognizer availabilityDidChange:(BOOL)available;
通过Block回调的方式进行语音识别请求十分简单,如果使用代理回调的方式,开发者需要实现SFSpeechRecognitionTaskDelegate协议中的相关方法,如下:
//当开始检测音频源中的语音时首先调用此方法
- (void)speechRecognitionDidDetectSpeech:(SFSpeechRecognitionTask *)task;
//当识别出一条可用的信息后 会调用
/*
需要注意,apple的语音识别服务会根据提供的音频源识别出多个可能的结果 每有一条结果可用 都会调用此方法
*/
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didHypothesizeTranscription:(SFTranscription *)transcription;
//当识别完成所有可用的结果后调用
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishRecognition:(SFSpeechRecognitionResult *)recognitionResult;
//当不再接受音频输入时调用 即开始处理语音识别任务时调用
- (void)speechRecognitionTaskFinishedReadingAudio:(SFSpeechRecognitionTask *)task;
//当语音识别任务被取消时调用
- (void)speechRecognitionTaskWasCancelled:(SFSpeechRecognitionTask *)task;
//语音识别任务完成时被调用
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishSuccessfully:(BOOL)successfully;
SFSpeechRecognitionTask类中封装了属性和方法如下:
//此任务的当前状态
/*
typedef NS_ENUM(NSInteger, SFSpeechRecognitionTaskState) {
SFSpeechRecognitionTaskStateStarting = 0, // 任务开始
SFSpeechRecognitionTaskStateRunning = 1, // 任务正在运行
SFSpeechRecognitionTaskStateFinishing = 2, // 不在进行音频读入 即将返回识别结果
SFSpeechRecognitionTaskStateCanceling = 3, // 任务取消
SFSpeechRecognitionTaskStateCompleted = 4, // 所有结果返回完成
};
*/
@property (nonatomic, readonly) SFSpeechRecognitionTaskState state;
//音频输入是否完成
@property (nonatomic, readonly, getter=isFinishing) BOOL finishing;
//手动完成音频输入 不再接收音频
- (void)finish;
//任务是否被取消
@property (nonatomic, readonly, getter=isCancelled) BOOL cancelled;
//手动取消任务
- (void)cancel;
关于音频识别请求类,除了可以使用SFSpeechURLRecognitionRequest类来进行创建外,还可以使用SFSpeechAudioBufferRecognitionRequest类来进行创建:
@interface SFSpeechAudioBufferRecognitionRequest : SFSpeechRecognitionRequest
@property (nonatomic, readonly) AVAudioFormat *nativeAudioFormat;
//拼接音频流
- (void)appendAudioPCMBuffer:(AVAudioPCMBuffer *)audioPCMBuffer;
- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer;
//完成输入
- (void)endAudio;
@end
五、语音识别结果类SFSpeechRecognitionResult
SFSpeechRecognitionResult类是语音识别结果的封装,其中包含了许多套平行的识别信息,其每一份识别信息都有可信度属性来描述其准确程度。SFSpeechRecognitionResult类中属性如下:
//识别到的多套语音转换信息数组 其会按照准确度进行排序
@property (nonatomic, readonly, copy) NSArraySFTranscription * *transcriptions;
//准确性最高的识别实例
@property (nonatomic, readonly, copy) SFTranscription *bestTranscription;
//是否已经完成 如果YES 则所有所有识别信息都已经获取完成
@property (nonatomic, readonly, getter=isFinal) BOOL final;
SFSpeechRecognitionResult类只是语音识别结果的一个封装,真正的识别信息定义在SFTranscription类中,SFTranscription类中属性如下:
//完整的语音识别准换后的文本信息字符串
@property (nonatomic, readonly, copy) NSString *formattedString;
//语音识别节点数组
@property (nonatomic, readonly, copy) NSArraySFTranscriptionSegment * *segments;
当对一句完整的话进行识别时,Apple的语音识别服务实际上会把这句语音拆分成若干个音频节点,每个节点可能为一个单词,SFTranscription类中的segments属性就存放这些节点。SFTranscriptionSegment类中定义的属性如下:
//当前节点识别后的文本信息
@property (nonatomic, readonly, copy) NSString *substring;
//当前节点识别后的文本信息在整体识别语句中的位置
@property (nonatomic, readonly) NSRange substringRange;
//当前节点的音频时间戳
@property (nonatomic, readonly) NSTimeInterval timestamp;
//当前节点音频的持续时间
@property (nonatomic, readonly) NSTimeInterval duration;
//可信度/准确度 0-1之间
@property (nonatomic, readonly) float confidence;
//关于此节点的其他可能的识别结果
@property (nonatomic, readonly) NSArrayNSString * *alternativeSubstrings;
温馨提示:SpeechFramework框架在模拟器上运行会出现异常情况,无法进行语音识别请求。会报出kAFAssistantErrorDomain的错误,还望有知道解决方案的朋友,给些建议,Thanks。
cocoa与cocoa Touch区别之分 相同之处:两者都包含OC运行时的两个核心框架: cocoa包含Foundation和AppKit框架,可用于开发Mac OS X系统的应用程序 cocoa touch包含Foundation和UIKit框架,可用于开发iPhone OS 系统的应用程序 Cocoa时Mac OS X...