使用場(chǎng)景
當(dāng)程序啟動(dòng)的時(shí)候幽崩,我們根據(jù)需要慌申,向用戶展示相關(guān)界面,比如升級(jí)彈窗咨油,開啟推送彈窗柒爵,廣告彈窗棉胀,活動(dòng)彈窗等等霎挟,但是如果不去有效的管理這些數(shù)據(jù)氓扛,就會(huì)導(dǎo)致數(shù)據(jù)沖突论笔,界面混亂,從而給用戶造成一些困擾,因此本文應(yīng)運(yùn)而生了整份。
解決整個(gè) APP 運(yùn)行時(shí)串行執(zhí)行任務(wù)源碼如下
串行執(zhí)行任務(wù)
簡介
Bolts 是由 Facebook and Parse open source 的 Framework. 主要實(shí)現(xiàn)是為了解決 async callbacks 問題的 Promise Pattern. Promise / Future 在很多語言或 library 都有實(shí)現(xiàn)籽孙。 ( JQuery, AngularJS, Java, Dart ...etc )
雖說是 Framework 但用法并不難而且類數(shù)也不多犯建。主要是以 BFTask
, BFTaskCompletionSource
, BFExector
這三個(gè)為主要。
假設(shè)我們要建立一個(gè)testAsync function竿开,必須要在最后回傳 BFTask
否彩,建立 task 的方式則使用BFTaskCompletionSource
的靜態(tài)方法來建立 task 并設(shè)定相關(guān)參數(shù)
- (BFTask *)testAsync {
NSLog(@"testAsync");
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
source.result = @"test async complete";
return source.task;
}
BFTaskCompletionSource
是 BFTask 的proxy object(代理對(duì)象)嗦随,當(dāng) function 完成時(shí)設(shè)定 task.result
枚尼,執(zhí)行后則視為task complete,task.result 是 id 動(dòng)態(tài)類型悬而,可以將處理完后需要的結(jié)果設(shè)定到task.result
锭汛,function 執(zhí)行時(shí)導(dǎo)致的錯(cuò)誤失敗設(shè)定為task.error
幕侠,如有執(zhí)行出現(xiàn)例外账锹,設(shè)定task.exception
蔚袍,如要取消task,調(diào)用[task cancel]即可.
無論是 error/exception/cancel 執(zhí)行完后皆視為 task complete. 另外, task 提供了一些方法來判斷目前狀況 isCancelled / isCompleted / error / exception. 也可以直接使用 [BFTask taskWithResult] / [BFTask taskWithError] / [BFTask taskWithException] / [BFTask cancelledTask] 靜態(tài)方法直接建立 task 來做相關(guān)處理晋辆。
實(shí)現(xiàn) Async function 之后瓶佳,可以一個(gè)接著一個(gè)將要調(diào)用的函數(shù)串起來稱之為Chain鳞青,在每一個(gè)函數(shù)回調(diào)的 task 執(zhí)行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 的方式來執(zhí)行 Task
- Series:以串行方式回調(diào)task執(zhí)行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 塞進(jìn)一個(gè) 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
來執(zhí)行,默認(rèn)是通過[BFExecutor defaultExexutor]
器仗,另外還有immediateExecutor
/ mainThreadExecutor
可供使用童番,基于 GCD 實(shí)現(xiàn)。
immediateExecutor
:是直接以GCD dispatch_once 執(zhí)行mainThreadExecutor
:以GCD dispatch_once 執(zhí)行轨香,檢查是否為isMainThread臂容,如果不是根蟹,則調(diào)用dispatch_async(dispatch_get_main_queue(),block);延后執(zhí)行defaultExecutor
:以GCD dispatch_once 執(zhí)行,會(huì)檢查current thread 的threadDictionary objectForKey:kBFTaskDepthKey索取出的 depth 是否超過20個(gè)球散,如果超過蕉堰,則dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); 延后執(zhí)行悲龟。否則會(huì)在當(dāng)前 Thread 將 depth + 1, try-catch 執(zhí)行完畢再 -1。
[[self testAsync] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
NSLog(@"task execute");
return nil;
}
例子如下所示
測(cè)試準(zhǔn)備
建立一個(gè) object 對(duì)象并聲明一個(gè)類方法返回 BFTask 對(duì)象
CSTask.h 文件
#import <Foundation/Foundation.h>
#import <Bolts/BFTaskCompletionSource.h>
@interface CSTask : NSObject
+ (BFTask *)taskWithResult:(NSString *)result;
@end
CSTask.m 實(shí)現(xiàn)文件
#import "CSTask.h"
@implementation CSTask
+ (BFTask *)taskWithResult:(NSString *)result {
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
source.result = result;
return source.task;
}
@end
編寫測(cè)試方法
- (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"];
}
串行執(zhí)行文件
- (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;
}];
}
執(zhí)行結(jié)果
測(cè)試串行
- (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;
}];
}
運(yùn)行結(jié)果
測(cè)試并行
- (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;
}];
}
運(yùn)行結(jié)果
項(xiàng)目實(shí)戰(zhàn)
一般我們需要在 APP 啟動(dòng)后依次彈一些彈窗進(jìn)行交互划乖,比如版本更新提示诀拭,活動(dòng)廣告等等煤蚌, 所以保證彈窗依次彈出并且不會(huì)重疊在一起就顯得很重要了。
詳情見范例
- StartupSerialTasks.h
#import <Foundation/Foundation.h>
#import <Bolts/BFTask.h>
#import <Bolts/BFTaskCompletionSource.h>
@interface StartupSerialTasks : NSObject
+ (StartupSerialTasks *)instance;
// 程序啟動(dòng)時(shí)需要做的事情
- (void)addStartupTask:(NSString *)key withBlock:(void (^)(BFTaskCompletionSource *promise))block;
@end
- StartupSerialTasks.m
@implementation StartupSerialTasks {
BFTask *_chainStartupTask; // 程序啟動(dòng)時(shí)需要做的事情
}
+ (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:@"程序啟動(dòng)執(zhí)行的任務(wù)"];
}
return self;
}
// 程序啟動(dòng)時(shí)需要做的事情
- (void)addStartupTask:(NSString *)key withBlock:(void (^)(BFTaskCompletionSource *promise))block {
_chainStartupTask = [_chainStartupTask continueWithBlock:^id _Nullable(BFTask *task) {
BFTaskCompletionSource *asyncRes = [BFTaskCompletionSource taskCompletionSource];
// 如果調(diào)用的是在主線程,這里則回調(diào)回主線程調(diào)用.
dispatch_async([NSThread isMainThread] ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue(), ^{
block(asyncRes);
});
return asyncRes.task;
}];
}
@end
外界使用:假設(shè)我們要依次執(zhí)行5個(gè)任務(wù),每個(gè)任務(wù)的執(zhí)行時(shí)間不確定翰苫,要求任務(wù)按照先后順序依次執(zhí)行这橙。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 一般解決程序啟動(dòng),依次顯示彈窗任務(wù)
for (int i = 0; i < 5; i++) {
[self startupTask:[NSString stringWithFormat:@"任務(wù)%d",i]];
}
}
// 開始執(zhí)行任務(wù)
- (void)startupTask:(NSString *)name {
[[StartupSerialTasks instance] addStartupTask:@"windowShow" withBlock:^(BFTaskCompletionSource *promise) {
NSUInteger time = arc4random_uniform(5);
NSLog(@"%@",[NSString stringWithFormat:@"開始執(zhí)行%@,執(zhí)行時(shí)間:%lu",name,time]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSString stringWithFormat:@"%@執(zhí)行完畢",name]);
[promise trySetResult:@YES];
});
}];
}
運(yùn)行結(jié)果
通過打印結(jié)果可知埃唯,任務(wù)一到任務(wù)五依次執(zhí)行鹰晨,執(zhí)行時(shí)間不固定,嚴(yán)格按照指定的順序執(zhí)行漠趁。
任務(wù)執(zhí)行完畢一定要將其設(shè)置為完成忍疾,
[promise trySetResult:@YES]
否則下一個(gè)任務(wù)不會(huì)接著執(zhí)行。
本文參考 kakukeme 的 Bolts framework iOS 筆記丸边,非常感謝該作者荚孵。