iOS Bolts Framework 笔记

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 执行continueWithBlockcontinueWithSuccessBlock

[[[[[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;
    }];
}

执行结果

image.png
测试串行
- (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;
    }];
}

运行结果

image.png
测试并行
- (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;
    }];
}

运行结果

image.png
项目实战

一般我们需要在 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];
        });
    }];
}

运行结果

image.png

通过打印结果可知,任务一到任务五依次执行,执行时间不固定,严格按照指定的顺序执行。

任务执行完毕一定要将其设置为完成,[promise trySetResult:@YES]否则下一个任务不会接着执行。


本文参考 kakukeme 的 Bolts framework iOS 笔记,非常感谢该作者。


项目连接地址 - Bolts

你可能感兴趣的:(iOS Bolts Framework 笔记)