Bolts/BFTask

Bolts

简介

自从Parse加盟Facebook后,他们发现很多细小的功能都分别在各自的sdk中实现了。于是他们决定开发一个更底层的库协调他们的sdk之间的工作。现在已经将这个库开源,这就是Bolts

组件

task

第一个组件就是task,这是为了解决async callbacks而产生的。BFTask的相关源码并不多,主要是以BFTask, BFTaskCompletionSource, BFExector 这三个为主。

BFTask的使用非常简单,如果开发过程中有一个可以异步执行的方法,那么就可以执行这个方法并且返回一个BFTask指针,然后在continuation block中处理这个异步方法执行后的操作。

  • 基本用法

    
    // Objective-C
    - (BFTask *) fetchAsync:(PFObject *)object {
     / *
      * 创建一个标示BFTask是否完成的类,BFTaskCompletionSource本身就含有一个BFTask.
      * 在下面的代码中object完成fetchInBackgroundWithBlock操作后,对taskSource中的task进行设置,标示这个task的完成情况,用于外部对这个task的后续处理。
      */
    BFTaskCompletionSource *taskSource = [BFTaskCompletionSource taskCompletionSource];
      
    [object fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
        if (!error) {
          [taskSource setResult:object];
        } else {
          [taskSource setError:error];
        }
      }];
      
     return taskSource.task;
    }
    
    // Objective-C
    [[self fetchAsync:obj] continueWithBlock:^id(BFTask *task) {
        
        if (task.result) {
            // fetchAsync task 成功
        }
        else if (task.error) {
            // fetchAsync task 失败
        }     
      return nil;
    }];
    
    // Objective-C
    [[self fetchAsync:obj] continueWithSuccessBlock:^id(BFTask *task) {
      // 如果只需要关心成功情况可以使用continueWithSuccessBlock
      return nil;
    }];
    
    
  • 链式用法

    
    // Objective-C
    PFQuery *query = [PFQuery queryWithClassName:@"Student"];
    [query orderByDescending:@"gpa"];
    [[[[[self findAsync:query] continueWithSuccessBlock:^id(BFTask *task) {
      NSArray *students = task.result;
      PFObject *valedictorian = [students objectAtIndex:0];
      [valedictorian setObject:@YES forKey:@"valedictorian"];
      return [self saveAsync:valedictorian];
    }] continueWithSuccessBlock:^id(BFTask *task) {
      PFObject *valedictorian = task.result;
      return [self findAsync:query];
    }] continueWithSuccessBlock:^id(BFTask *task) {
      NSArray *students = task.result;
      PFObject *salutatorian = [students objectAtIndex:1];
      [salutatorian setObject:@YES forKey:@"salutatorian"];
      return [self saveAsync:salutatorian];
    }] continueWithSuccessBlock:^id(BFTask *task) {
      // Everything is done!
      return nil;
    }];
    
    
  • 串行任务

    
    / *
      * 通过PFQuery进行异步查询,然后将查询结果异步删除,并且删除操作是串行的.
     */
    
    // Objective-C
    PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
    [query whereKey:@"post" equalTo:@123];
    
    [[[self findAsync:query] continueWithBlock:^id(BFTask *task) {
      NSArray *results = task.result;
    
      // 创建一个开始的任务,之后的每一个deleteAsync操作都会依次在这个任务之后顺序进行.
     BFTask *task = [BFTask taskWithResult:nil];
     for (PFObject *result in results) {
        // For each item, extend the task with a function to delete the item.
        task = [task continueWithBlock:^id(BFTask *task) {
          // Return a task that will be marked as completed when the delete is finished.
          return [self deleteAsync:result];
        }];
      }
      // 返回的是最后一个deleteAsync操作的task
      return task;
    }] continueWithBlock:^id(BFTask *task) {
      // Every comment was deleted.
      return nil;
    }];
    
    
  • 并行任务

    
    / *
      * 通过PFQuery进行异步查询,然后将查询结果异步删除,并且删除操作是并行的.
     */
     
    // Objective-C
    PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
    [query whereKey:@"post" equalTo:@123];
    
    [[[self findAsync:query] continueWithBlock:^id(BFTask *results) {
      // Collect one task for each delete into an array.
      NSMutableArray *tasks = [NSMutableArray array];
      for (PFObject *result in results) {
        // Start this delete immediately and add its task to the list.
        [tasks addObject:[self deleteAsync:result]];
      }
      
      // 所有的删除任务合在一起本身也是一个任务,删除任务之前是并行的
      return [BFTask taskForCompletionOfAllTasks:tasks];
    }] continueWithBlock:^id(BFTask *task) {
      // Every comment was deleted.
      return nil;
    }]; 
        
    
  • 任务执行者

    BFExecutor 是 BFTask的执行者,默认的BFExecutor是立即在当前线程中执行的,但是如果call stack太深,会异步dispatch到global queue上去执行。
    如果不用默认的执行者,还可以指定执行queue,或者是NSOperationQueue,包括主线程。

    
    // 创建 BFExecutor
    BFExecutor *myExecutor = [BFExecutor executorWithBlock:^void(void(^block)()) {
      dispatch_async(dispatch_get_main_queue(), block);
    }];
    
    BFExecutor *myExecutor = [BFExecutor mainThreadExecutor];
    
    // And use the Main Thread Executor like this. The executor applies only to the new
    // continuation being passed into continueWithBlock.
    [[self fetchAsync:object] continueWithExecutor:myExecutor withBlock:^id(BFTask *task) {
        myTextView.text = [object objectForKey:@"name"];
    }];
    
    
  • 任务取消

    我们在直接使用GCD的时候很难把一个block任务取消。BFCancellationTokenSource就很方便的实现了任务取消功能。

      /*
      * 执行任务的时候传入BFCancellationTokenSource,在任务还没有执行的时候设置取消标示,
      * 该任务的block不会被执行
      */
      
     BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource];
     
     /// 任务延迟执行100毫秒
     BFTask *task = [BFTask taskWithDelay:100];
        
     task = [task continueWithExecutor:[BFExecutor immediateExecutor]
                  block:^id(BFTask *t) {
                    NSLog(@"Continuation block should not be triggered");
    
                    return nil;
                }
                    cancellationToken:cts.token];
      
     /// 设置取消标示  
    [cts cancel];
    /// 等待任务完成 (这个代码一般不在主线程中使用)
    [task waitUntilFinished];
    
    
    
    /*
      * 也可以在continueWithBlock中自己实现对cancellationToken的判断,取消任务
      */
    - (void)doSomethingComplicatedAsync:(MYCancellationToken *)cancellationToken {
        [[self doSomethingAsync:cancellationToken] continueWithBlock:^{
            if (cancellationToken.isCancelled) {
                return [BFTask cancelledTask];
            }
            // Do something that takes a while.
            return result;
        }];
    }
    
    // Somewhere else.
    MYCancellationToken *cancellationToken = [[MYCancellationToken alloc] init];
    [obj doSomethingComplicatedAsync:cancellationToken];
    
    // When you get bored...
    [cancellationToken cancel];
    
    
  • 关键源码解析

    BFTask的关键代码就是下面这个函数。

        
    /*
     * 在指定的BFExecutor中执行block代码
     */
    - (BFTask *)continueWithExecutor:(BFExecutor *)executor
                           block:(BFContinuationBlock)block
               cancellationToken:(nullable BFCancellationToken *)cancellationToken {
               
    // 创建BFTaskCompletionSource,返回source的task。本次执行block的操作也是一个需要返回的任务。
    BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
    
    // 定义执行的executionBlock
    dispatch_block_t executionBlock = ^{
          // 如果cancellationToken标示位取消,就直接把tcs中的task设置为取消,不会继续执行BFContinuationBlock,实现了对block的取消操作。
        if (cancellationToken.cancellationRequested) {
            [tcs cancel];
            return;
        }
    
        id result = nil;
    

pragma clang diagnostic push

pragma clang diagnostic ignored "-Wdeprecated-declarations"

    if (BFTaskCatchesExceptions()) {
        @try {
                // 执行BFContinuationBlock,如果block中抛出异常就把tcs中的task设置tcs中task为异常,不再继续执行
            result = block(self);
        } @catch (NSException *exception) {
            tcs.exception = exception;
            return;
        }
    } else {
        result = block(self);
    }

pragma clang diagnostic pop

     // 如果block也返回一个BFTask,如果BFTask已经结束了,执行setupWithTask(BFTask)
     // 以BFTask的结果作为tcs的结果。如果BFTask没有结束,就等BFTask结束后再
     // 执行setupWithTask block
    if ([result isKindOfClass:[BFTask class]]) {

        id (^setupWithTask) (BFTask *) = ^id(BFTask *task) {
            if (cancellationToken.cancellationRequested || task.cancelled) {
                [tcs cancel];

pragma clang diagnostic push

pragma clang diagnostic ignored "-Wdeprecated-declarations"

            } else if (task.exception) {
                tcs.exception = task.exception;

pragma clang diagnostic pop

            } else if (task.error) {
                tcs.error = task.error;
            } else {
                tcs.result = task.result;
            }
            return nil;
        };

        BFTask *resultTask = (BFTask *)result;

        if (resultTask.completed) {
            setupWithTask(resultTask);
        } else {
            [resultTask continueWithBlock:setupWithTask];
        }

    } else {
        tcs.result = result;
    }
};

BOOL completed;

// 如果self没有completed,就把执行executionBlock的操作放在callbacks里。
// task中对callbacks的访问需要加锁
@synchronized(self.lock) {
    completed = self.completed;
    if (!completed) {
        [self.callbacks addObject:[^{
            [executor execute:executionBlock];
        } copy]];
    }
}
if (completed) {
    [executor execute:executionBlock];
}

return tcs.task;

}

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,569评论 18 139
  • 发现写博客想写明白也是一件不容易的事情。 这次拿YYKIt 源码 分析分析。希望这次能写的更好些。 YYKit 系...
    充满活力的早晨阅读 6,539评论 4 16
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 第一篇第二篇大概是把下载图片缓存图片的这个逻辑走完了,里面涉及好多类。 罗列一下 UIView+WebCache ...
    充满活力的早晨阅读 733评论 0 1
  • 技术无极限,从菜鸟开始,从源码开始。 由于公司目前项目还是用OC写的项目,没有升级swift 所以暂时SDWebI...
    充满活力的早晨阅读 12,611评论 0 2