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 <Foundation/Foundation.h>
#import <Bolts/BFTaskCompletionSource.h>

@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 <Foundation/Foundation.h>
#import <Bolts/BFTask.h>
#import <Bolts/BFTaskCompletionSource.h>

@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<UITouch *> *)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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,362评论 8 265
  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 797评论 0 3
  • 1.NSTimer不准时的原因:(1).RunLoop循环处理时间,每次循环是固定时间,只有在这段时间才会去查看N...
    稻春阅读 1,240评论 0 3
  • 没有一点点防备,也没有一丝顾虑。你就这样的出现,在我的世界里,带给我惊喜,情不自已。来来来,掌声欢迎一下亲戚,姨妈...
    贺小妮阅读 9,370评论 110 79
  • 自己是个理科生,脑子里多的是数字和逻辑,少的是修辞和漫想。一直认为自己不适合写文章,不适合去做一些和文章有关...
    亦小木阅读 204评论 0 0