Bolts gitHub 连接地址
使用场景
当程序启动的时候,我们根据需要,向用户展示相关界面,比如升级弹窗,开启推送弹窗,广告弹窗,活动弹窗等等,但是如果不去有效的管理这些数据,就会导致数据冲突,界面混乱,从而给用户造成一些困扰,因此本文应运而生了。
解决整个 APP 运行时串行执行任务源码如下
串行执行任务
简介
Bolts 是由 Facebook and Parse open source 的 Framework. 主要实现是为了解决 async callbacks 问题的 Promise Pattern. Promise / Future 在很多语言或 library 都有实现。 ( JQuery, AngularJS, Java, Dart ...etc )
虽说是 Framework 但用法并不难而且类数也不多。主要是以 BFTask
, BFTaskCompletionSource
, BFExector
这三个为主要。
假设我们要建立一个testAsync function,必须要在最后回传 BFTask
,建立 task 的方式则使用BFTaskCompletionSource
的静态方法来建立 task 并设定相关参数
- (BFTask *)testAsync {
NSLog(@"testAsync");
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
source.result = @"test async complete";
return source.task;
}
BFTaskCompletionSource
是 BFTask 的proxy object(代理对象),当 function 完成时设定 task.result
,执行后则视为task complete,task.result 是 id 动态类型,可以将处理完后需要的结果设定到task.result
,function 执行时导致的错误失败设定为task.error
,如有执行出现例外,设定task.exception
,如要取消task,调用[task cancel]即可.
无论是 error/exception/cancel 执行完后皆视为 task complete. 另外, task 提供了一些方法来判断目前状况 isCancelled / isCompleted / error / exception. 也可以直接使用 [BFTask taskWithResult] / [BFTask taskWithError] / [BFTask taskWithException] / [BFTask cancelledTask] 静态方法直接建立 task 来做相关处理。
实现 Async function 之后,可以一个接着一个将要调用的函数串起来称之为Chain,在每一个函数回调的 task 执行continueWithBlock
或continueWithSuccessBlock
。
[[[[[self testAsync] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"%@",task.result);
return [self testAsync1];
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"%@",task.result);
return [self testAsync2];
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"%@",task.result);
return [self testAsync3];
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"%@",task.result);
NSLog(@"task in chain complete");
return nil;
}];
另外还可以以Series / Parallel 的方式来执行 Task
- Series:以串行方式回调task执行continueWithBlock
- (void)seriesTask {
[[[self testAsync] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
BFTask *_task = [BFTask taskWithResult:nil];
for (int i = 0; i < 3; i++) {
task = [task continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
return [self testAsync];
}];
}
return _task;
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"tasks execute in series complete");
return nil;
}];
}
- Parallel:将所有 task 塞进一个 array后丢给
taskForCompletionOfAllTasks
处理
- (void)parallelTask {
[[[self testAsync] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSMutableArray *tasks = [NSMutableArray array];
for (int i = 0; i < 3; i++) {
[tasks addObject:[self testAsync]];
}
[tasks addObject:[self testAsync]];
return [BFTask taskForCompletionOfAllTasks:tasks];
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"tasks execute in series complete");
return nil;
}];
}
BFTask
主要是通过BFExecutor
来执行,默认是通过[BFExecutor defaultExexutor]
,另外还有immediateExecutor
/ mainThreadExecutor
可供使用,基于 GCD 实现。
immediateExecutor
:是直接以GCD dispatch_once 执行mainThreadExecutor
:以GCD dispatch_once 执行,检查是否为isMainThread,如果不是,则调用dispatch_async(dispatch_get_main_queue(),block);延后执行defaultExecutor
:以GCD dispatch_once 执行,会检查current thread 的threadDictionary objectForKey:kBFTaskDepthKey索取出的 depth 是否超过20个,如果超过,则dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); 延后执行。否则会在当前 Thread 将 depth + 1, try-catch 执行完毕再 -1。
[[self testAsync] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
NSLog(@"task execute");
return nil;
}
例子如下所示
测试准备
建立一个 object 对象并声明一个类方法返回 BFTask 对象
CSTask.h 文件
#import
#import
@interface CSTask : NSObject
+ (BFTask *)taskWithResult:(NSString *)result;
@end
CSTask.m 实现文件
#import "CSTask.h"
@implementation CSTask
+ (BFTask *)taskWithResult:(NSString *)result {
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
source.result = result;
return source.task;
}
@end
编写测试方法
- (BFTask *)testAsync {
NSLog(@"testAsync");
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
source.result = @"test async complete";
return source.task;
}
- (BFTask *)testAsync1 {
NSLog(@"testAsync1");
return [CSTask taskWithResult:@"test async1 complete"];
}
- (BFTask *)testAsync2 {
NSLog(@"testAsync2");
return [CSTask taskWithResult:@"test async2 complete"];
}
- (BFTask *)testAsync3 {
NSLog(@"testAsync3");
return [CSTask taskWithResult:@"test async3 complete"];
}
串行执行文件
- (void)testTask {
[[[[[self testAsync] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"%@",task.result);
return [self testAsync1];
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"%@",task.result);
return [self testAsync2];
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"%@",task.result);
return [self testAsync3];
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"%@",task.result);
NSLog(@"task in chain complete");
return nil;
}];
}
执行结果
测试串行
- (void)seriesTask {
[[[self testAsync] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
BFTask *_task = [BFTask taskWithResult:nil];
for (int i = 0; i < 3; i++) {
task = [task continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
return [self testAsync1];
}];
}
return _task;
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"tasks execute in series complete");
NSLog(@"%@",task.result);
return nil;
}];
}
运行结果
测试并行
- (void)parallelTask {
[[[self testAsync] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSMutableArray *tasks = [NSMutableArray array];
for (int i = 0; i < 3; i++) {
[tasks addObject:[self testAsync1]];
}
[tasks addObject:[self testAsync2]];
return [BFTask taskForCompletionOfAllTasks:tasks];
}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
NSLog(@"tasks execute in series complete");
NSLog(@"%@",task.result);
return nil;
}];
}
运行结果
项目实战
一般我们需要在 APP 启动后依次弹一些弹窗进行交互,比如版本更新提示,活动广告等等, 所以保证弹窗依次弹出并且不会重叠在一起就显得很重要了。
详情见范例
- StartupSerialTasks.h
#import
#import
#import
@interface StartupSerialTasks : NSObject
+ (StartupSerialTasks *)instance;
// 程序启动时需要做的事情
- (void)addStartupTask:(NSString *)key withBlock:(void (^)(BFTaskCompletionSource *promise))block;
@end
- StartupSerialTasks.m
@implementation StartupSerialTasks {
BFTask *_chainStartupTask; // 程序启动时需要做的事情
}
+ (StartupSerialTasks *)instance {
static StartupSerialTasks *_instance = nil;
@synchronized (self) {
if (_instance == nil) {
_instance = [[self alloc] init];
}
}
return _instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_chainStartupTask = [BFTask taskWithResult:@"程序启动执行的任务"];
}
return self;
}
// 程序启动时需要做的事情
- (void)addStartupTask:(NSString *)key withBlock:(void (^)(BFTaskCompletionSource *promise))block {
_chainStartupTask = [_chainStartupTask continueWithBlock:^id _Nullable(BFTask *task) {
BFTaskCompletionSource *asyncRes = [BFTaskCompletionSource taskCompletionSource];
// 如果调用的是在主线程,这里则回调回主线程调用.
dispatch_async([NSThread isMainThread] ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue(), ^{
block(asyncRes);
});
return asyncRes.task;
}];
}
@end
外界使用:假设我们要依次执行5个任务,每个任务的执行时间不确定,要求任务按照先后顺序依次执行。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 一般解决程序启动,依次显示弹窗任务
for (int i = 0; i < 5; i++) {
[self startupTask:[NSString stringWithFormat:@"任务%d",i]];
}
}
// 开始执行任务
- (void)startupTask:(NSString *)name {
[[StartupSerialTasks instance] addStartupTask:@"windowShow" withBlock:^(BFTaskCompletionSource *promise) {
NSUInteger time = arc4random_uniform(5);
NSLog(@"%@",[NSString stringWithFormat:@"开始执行%@,执行时间:%lu",name,time]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSString stringWithFormat:@"%@执行完毕",name]);
[promise trySetResult:@YES];
});
}];
}
运行结果
通过打印结果可知,任务一到任务五依次执行,执行时间不固定,严格按照指定的顺序执行。
任务执行完毕一定要将其设置为完成,
[promise trySetResult:@YES]
否则下一个任务不会接着执行。
本文参考 kakukeme 的 Bolts framework iOS 笔记,非常感谢该作者。
项目连接地址 - Bolts