Facebook的BFTask简介

要创建一个真正具有响应式的iOS应用程序,你必须保持长时间的运行以便操作UI线程,并注意避免任何延迟事件阻塞UI线程。这意味着你将需要在后台执行各种操作。为方便起见,我们增加了一个类BFTask。一个可以表示异步操作结果的任务(task)。通常情况下,一个BFTask从异步函数返回,并给出一个继续处理任务结果的能力。当一个任务从一个函数返回它已经开始做的工作。一个任务是不依赖于特定的线程模型:它代表正在做的工作,而不是在那里执行。task比异步编程的其它方法,诸如回调有更多优点。 BFTask是不能代替NSOperation或GCD的。事实上,他们发挥得很好。但是BFTask填补了它们中的一些空白,以及这些技术没有解决​​的一些问题。

BFTask负责管理依赖。与使用NSOperation的依赖管理相比,你没有必要开始BFTask之前声明的所有依赖关系。例如,假设你需要保存一组对象,每个人可能或不可能需要保存子对象。在NSOperation,您通常需要提前创建操作为每个子操作节省时间。但是,你总是不知道你何时开始工作,是否它是必要的。这让管理依赖关系的NSOperation非常痛苦。即使在最好的情况下,必须依赖于它们的操作,导致出现了比它执行不同的命令代码之前创建的依赖。使用BFTask,您可以在操作的过程中决定是否将子任务返回,在一个很短的时间内。

BFTasks释放依赖关系。NSOperation强引用了这些依赖项,所以如果你有队列顺序地执行这些依赖项,你的应用将会产生泄漏,因为每次的操作永远会被retain。BFTasks会释放它们的回调,当这些操作在运行中的时候,所以清理BFTask后,它持有的一切东西都将被清空。这可以减少内存使用,并简化内存管理。

BFTasks跟踪完成任务的状态:它跟踪是否有返回值,任务被取消,或者如果发生错误。它还具有方便的方法来返回错误信息。在NSOperation里,你自己必须建立所有这些东西。

BFTasks不依赖于任何特定的线程模型。所以它可以简单地使用一个操作队列执行几个任务,当其他的操作使用block或者GCD的时候。这些任务可以互相依赖并且无缝连接。

在连续执行多个任务时无需创建嵌套的“金字塔”的代码,你会只使用回调。

BFTasks是完全可组合的,允许您执行分支,并行和复杂的错误处理,而无需命名很多回调的意大利面条式(意指包含复杂庞大控制结构,杂乱无章的代码,特别是大量使用goto语句的代码等等)代码。

你可以安排基于任务的代码的顺序来执行,而不必使用分散的回调函数分开你的逻辑。

对于本文档中的例子,假设有异步版本的一些常用的解析方法,称为saveAsync:和findAsync:它返回一个Task。在后面的章节中,我们将介绍如何将自己定义这些功能。

The continueWithBlock Method

每个BFTask都有一个方法continueWithBlock: 包含有一个延时的block。当任务完成后,这个block将会执行。然后,您可以检查任务是否成功,并获得其结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// Objective-C
[[self saveAsync:obj] continueWithBlock:^id(BFTask *task) {
if (task.isCancelled) {
// the save was cancelled.
} else if (task.error) {
// the save failed.
} else {
// the object was saved successfully.
PFObject *object = task.result;
}
return nil;
}];

// Swift
self.saveAsync(obj).continueWithBlock {
(task: BFTask!) -> BFTask in
if task.isCancelled() {
// the save was cancelled.
} else if task.error() {
// the save failed.
} else {
// the object was saved successfully.
var object = task.result() as PFObject
}
}

BFTasks使用Objective-C的block,所以语法应该是非常简单的。让我们来看看一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

// Objective-C
//异步获取一个NSString。
- (BFTask *)getStringAsync {
//我们假设getNumberAsync返回BFTask,其结果是一个NSNumber。
return [[self getNumberAsync] continueWithBlock:^id(BFTask *task) {
//这个延续块返回的NSNumber BFTask作为输入,
//并提供一个NSString作为输出。
NSNumber *number = task.result;
return [NSString stringWithFormat:@"%@", number];
)];
}

// Swift
//Gets an NSString asynchronously.
func getStringAsync() -> BFTask {
//Let's suppose getNumberAsync returns a BFTask whose result is an NSNumber.
return self.getNumberAsync().continueWithBlock {
(task: BFTask!) -> NSString in
// This continuation block takes the NSNumber BFTask as input,
// and provides an NSString as output.
let number = task.result() as NSNumber
return NSString(format:"%@", number)
}
}

在很多情况下,你希望做更多的工作,如果之前的任务并没有传递任何错误或取消操作,那么它将会走成功的回调。要做到这一点,使用continueWithSuccessBlock:方法,而不是continueWithBlock :

1
2
3
4
5
6
7
8
9
10
11
12
13
14

// Objective-C
[[self saveAsync:obj] continueWithSuccessBlock:^id(BFTask *task) {
// the object was saved successfully.
return nil;
}];

// Swift
self.saveAsync(obj).continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
// the object was saved successfully.
return nil
}

Chaining Tasks Together

BFTasks是有点不可思议,因为它们允许你把它们链接起来而无需嵌套。如果从continueWithBlock返回BFTask : 然后continueWithBlock返回的任务将不被视为完成,直到新任务的新的连续的block返回之后。这使您可以避免金字塔式代码,你将获得回调与执行多个操作。同样,你可以从continueWithSuccessBlock返回BFTask:。因此,返回的BFTask将做更多的异步工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

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

// Swift
var query = PFQuery(className:"Student")
query.orderByDescending("gpa")
findAsync(query).continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var valedictorian = students.objectAtIndex(0) as PFObject
valedictorian["valedictorian"] = true
return self.saveAsync(valedictorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
var valedictorian = task.result() as PFObject
return self.findAsync(query)
}.continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var salutatorian = students.objectAtIndex(1) as PFObject
salutatorian["salutatorian"] = true
return self.saveAsync(salutatorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
// Everything is done!
return nil
}

Error Handling(错误处理)

通过仔细选择是否调用continueWithBlock:或continueWithSuccessBlock :,可以控制应用程序中的错误传播。使用continueWithBlock:您可以通过他们来处理错误信息。你可以把失败的任务当作抛出异常。事实上,如果你在continue回调时抛出(返回)一个异常,由此产生的后续任务将会产生故障与异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

// 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 [BFTask taskWithError:[NSError errorWithDomain:@"example.com"
code:-1
userInfo:nil]];
}] continueWithSuccessBlock:^id(BFTask *task) {
//现在,这个continue将被跳过。
PFQuery *valedictorian = task.result;
return [self findAsync:query];
}] continueWithBlock:^id(BFTask *task) {
if (task.error) {
//这个错误处理程序将被调用。
// 该错误将是上面返回的error.
//让我们通过返回一个新值处理错误。
//该任务将以nil作为其值返回。
return nil;
}
//这里也将被跳过。
NSArray *students = task.result;
PFObject *salutatorian = [students objectAtIndex:1];
[salutatorian setObject:@YES forKey:@"salutatorian"];
return [self saveAsync:salutatorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
//一切都完成了!这里被调用。
//任务的结果是nil
return nil;
}];

// Swift
var query = PFQuery(className:"Student")
query.orderByDescending("gpa")
findAsync(query).continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var valedictorian = students.objectAtIndex(0) as PFObject
valedictorian["valedictorian"] = true
//Force this callback to fail.
return BFTask(error:NSError(domain:"example.com",
code:-1, userInfo: nil))
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
//Now this continuation will be skipped.
var valedictorian = task.result() as PFObject
return self.findAsync(query)
}.continueWithBlock {
(task: BFTask!) -> AnyObject! in
if task.error() {
// This error handler WILL be called.
// The error will be the NSError returned above.
// Let's handle the error by returning a new value.
// The task will be completed with nil as its value.
return nil
}
// This will also be skipped.
let students = task.result() as NSArray
var salutatorian = students.objectAtIndex(1) as PFObject
salutatorian["salutatorian"] = true
return self.saveAsync(salutatorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
// Everything is done! This gets called.
// The tasks result is nil.
return nil
}


这是很方便的,在最后始终保持成功的一个回调链,而且只有唯一一个错误处理程序。

Creating Tasks(创建任务)

当你入门之后,你可以使用来自像findAsync方法返回的任务:或saveAsync : 然而,对于更高级的方案,你可能想使用自己的任务。要做到这一点,您可以创建一个BFTaskCompletionSource。这个对象可以让你创建一个新的BFTask,并控制其是否被标记为已完成或取消。创建BFTask后,您将需要调用的setResult : setError:或取消触发它的continuations。有了这些工具,你会很容易使自己的异步函数返回任务。例如,你可以使用fetchAsync的基于任务的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

- (BFTask *) fetchAsync:(PFObject *)object {
BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
[object fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
[task setResult:object];
} else {
[task setError:error];
}
}];
return task.task;
}

// Swift
func fetchAsync(object: PFObject) -> BFTask {
var task = BFTaskCompletionSource()
object.fetchInBackgroundWithBlock {
(object: PFObject?, error: NSError?) -> Void in
if error == nil {
task.setResult(object)
} else {
task.setError(error)
}
}
return task.task
}

同样地,很容易创建saveAsync : findAsync:或deleteAsync :

Tasks in Series(系列任务)

BFTasks很方便,当你想要执行一个串行的任务,每个任务都会等待上一个任务完成。例如,假设你要删除你的博客上所有的评论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
[query whereKey:@"post" equalTo:@123];

[[[self findAsync:query] continueWithBlock:^id(BFTask *task) {
NSArray *results = task.result;

/ /创建一个简单的完成任务的基本情况。
BFTask *task = [BFTask taskWithResult:nil];
for (PFObject *result in results) {
//对于每个对象,继续任务与函数删除的项目。
task = [task continueWithBlock:^id(BFTask *task) {
//返回一个已完成的被标记的任务,当删除完成的时候。

return [self deleteAsync:result];
}];
}
return task;
}] continueWithBlock:^id(BFTask *task) {
//每个评论都被删除。

return nil;
}];

// Swift
var query = PFQuery(className:"Comments")
query.whereKey("post", equalTo:123)
findAsync(query).continueWithBlock {
(task: BFTask!) -> BFTask in
let results = task.result() as NSArray

// Create a trivial completed task as a base case.
let task = BFTask(result:nil)
for result : PFObject in results {
// For each item, extend the task with a function to delete the item.
task = task.continueWithBlock {
(task: BFTask!) -> BFTask in
return self.deleteAsync(result)
}
}
return task
}.continueWithBlock {
(task: BFTask!) -> AnyObject! in
// Every comment was deleted.
return nil
}

// Swift
var query = PFQuery(className:"Comments")
query.whereKey("post", equalTo:123)
findAsync(query).continueWithBlock {
(task: BFTask!) -> BFTask in
let results = task.result() as NSArray

// Create a trivial completed task as a base case.
let task = BFTask(result:nil)
for result : PFObject in results {
// For each item, extend the task with a function to delete the item.
task = task.continueWithBlock {
(task: BFTask!) -> BFTask in
return self.deleteAsync(result)
}
}
return task
}.continueWithBlock {
(task: BFTask!) -> AnyObject! in
// Every comment was deleted.
}

Tasks in Parallel(并行任务)

您还可以同时执行多个任务,使用taskForCompletionOfAllTask​​s:方法。您可以一次启动多个作业,并且,使用taskForCompletionOfAllTask​​s:将会创建一个新的被标记完成的任务,当所有的任务都完成时。只有当所有传入的任务取得成功,新的任务才算是成功。并行执行的操作会比串行时更快,但可能会消耗更多的系统资源和带宽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

// Objective-C
PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
[query whereKey:@"post" equalTo:@123];

[[[self findAsync:query] continueWithBlock:^id(BFTask *results) {
//收集执行删除任务的Task到一个数组。
NSMutableArray *tasks = [NSMutableArray array];
for (PFObject *result in results) {
//开始立即删除,并添加到任务列表。
[tasks addObject:[self deleteAsync:result]];
}
//返回将被标记为完成时,当所有的删除动作完成时.
return [BFTask taskForCompletionOfAllTasks:tasks];
}] continueWithBlock:^id(BFTask *task) {
// 每个评论都被删除
return nil;
}];

// Swift
var query = PFQuery(className:"Comments")
query.whereKey("post", equalTo:123)

findAsync(query).continueWithBlock {
(task: BFTask!) -> BFTask in
// Collect one task for each delete into an array.
var tasks = NSMutableArray.array()
var results = task.result() as NSArray
for result : PFObject! in results {
// Start this delete immediately and add its task to the list.
tasks.addObject(self.deleteAsync(result))
}
// Return a new task that will be marked as completed when all of the deletes
// are finished.
return BFTask(forCompletionOfAllTasks:tasks)
}.continueWithBlock {
(task: BFTask!) -> AnyObject! in
// Every comment was deleted.
return nil
}

Task Executors(任务执行器)

无论continueWithBlock:和continueWithSuccessBlock:这些方法都有另一种形式,采用BFExecutor的一个实例。这些都是continueWithExecutor:withBlock:和continueWithExecutor:withSuccessBlock : 这些方法允许你控制延续事件是如何执行的。默认的执行器将会使用GCD,但你可以提供自己的执行器分派工作到不同的线程。例如,如果你想继续在UI线程上工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//创建一个使用主线程BFExecutor。
BFExecutor *myExecutor = [BFExecutor executorWithBlock:^void(void(^block)()) {
dispatch_async(dispatch_get_main_queue(), block);
}];

//而使用这样的主线程执行程序。执行程序仅适用于新的延续事件,通过continueWithBlock。
[[self fetchAsync:object] continueWithExecutor:myExecutor withBlock:^id(BFTask *task) {
myTextView.text = [object objectForKey:@"name"];
}];

一般的情况下,如在主线程上调用,我们提供BFExecutor的默认实现。这些实现包括defaultExecutor,immediateExecutor,mainThreadExecutor,executorWithDispatchQueue:和executorWithOperationQueue : 例如:

//继续在主线程,采用了内置的执行者。
[[self fetchAsync:object] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
myTextView.text = [object objectForKey:@"name"];
}];

Task Cancellation(取消任务)

这不是一个好的设计:用来跟踪BFTaskCompletionSource的注销与否。一个更好的模型是建立一个“取消标记”的顶层,并传递给你想做相同的“取消操作”的一部分,为每个异步函数。然后,在你的continuation blocks中,你可以检查取消标记是否已被取消,如果取消则返回[BFTask cancelledTask].例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

- (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];


注:取消标记实现应该是线程安全的。我们很可能会…像这样加入一些概念到Bolts在将来的某个时候。