如果您的NSOperation 是 AFHTTPRequestOperation 的子类,这很重要。 AFHTTPRequestOperation 在方法setCompletionBlockWithSuccess:failure 中将NSOperation 的属性completionBlock 用于其自身目的。在这种情况下,不要自己设置属性completionBlock!
看来,AFHTTPRequestOperation 的成功和失败处理程序会在主线程上运行。
否则,NSOperation 的完成块的执行上下文是“未定义的”。这意味着,完成块可以在任何线程/队列上执行。事实上,它在一些私有队列上执行。
IMO,这是首选方法,除非调用站点明确指定执行上下文。在实例可访问的线程或队列(例如主线程)上执行完成处理程序很容易被粗心的开发人员导致死锁。
编辑:
如果你想在父操作的完成块完成后开始一个依赖操作,你可以通过使完成块内容来解决这个问题 本身是一个NSBlockOperation(一个新的父级)并将此操作作为依赖项添加到子级操作并在队列中启动它。不过,您可能会意识到,这很快就会变得笨拙。
另一种方法需要一个实用程序类或类库,它特别适合以更简洁和容易的方式解决异步问题。 ReactiveCocoa 能够解决这样的(一个简单的)问题。然而,它过于复杂,而且实际上有一条“学习曲线”——而且是一条陡峭的曲线。我不会推荐它,除非你同意花几周的时间来学习它并且有很多其他异步用例,甚至更复杂的用例。
更简单的方法是使用在 JavaScript、Python、Scala 和其他一些语言中很常见的“Promises”。
现在,请仔细阅读,(简单的)解决方案实际上如下:
“Promises”(有时称为 Futures 或 Deferred)表示异步任务的最终结果。您的 fetch 请求就是这样的异步任务。但是,异步方法/任务返回一个Promise,而不是指定一个完成处理程序:
-(Promise*) fetchThingsWithURL:(NSURL*)url;
您通过注册成功处理程序块或失败处理程序块获得结果或错误,如下所示:
Promise* thingsPromise = [self fetchThingsWithURL:url];
thingsPromise.then(successHandlerBlock, failureHandlerBlock);
或者,内联的块:
thingsPromise.then(^id(id things){
// do something with things
return <result of success handler>
}, ^id(NSError* error){
// Ohps, error occurred
return <result of failure handler>
});
而且更短:
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil);
这里,parseAsync: 是一个异步方法,它返回一个 Promise。 (是的,一个承诺)。
您可能想知道如何从解析器中获取结果?
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
NSLog(@"Parser returned: %@", parserResult);
return nil; // result not used
}, nil);
这实际上启动了异步任务fetchThingsWithURL:。然后成功完成后,它会启动异步任务parseAsync:。然后当这成功完成时,它会打印结果,否则它会打印错误。
依次调用多个异步任务,一个接一个,称为“继续”或“链接”。
请注意,上面的整个语句是异步的!也就是说,当你将上面的语句包装成一个方法,并执行它时,该方法立即返回。
您可能想知道如何捕获任何错误,例如fetchThingsWithURL: 失败或parseAsync::
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
NSLog(@"Parser returned: %@", parserResult);
return nil; // result not used
}, nil)
.then(/*succes handler ignored*/, ^id (NSError* error){
// catch any error
NSLog(@"ERROR: %@", error);
return nil; // result not used
});
处理程序执行相应的任务已经完成(当然)。如果任务成功,将调用成功处理程序(如果有)。如果任务失败,将调用错误处理程序(如果有)。
处理程序可能返回一个 Promise(或任何其他对象)。例如,如果一个异步任务成功完成,它的成功处理程序将被调用,这将启动另一个异步任务,该任务返回承诺。当这完成后,又可以开始另一个,如此力量。那是“继续”;)
您可以从处理程序返回任何内容:
Promise* finalResult = [self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
return @"OK";
}, ^id(NSError* error){
return error;
});
现在,finalResult 最终将成为值 @"OK" 或 NSError。
您可以将最终结果保存到数组中:
array = @[
[self task1],
[self task2],
[self task3]
];
然后在所有任务都成功完成后继续:
[Promise all:array].then(^id(results){
...
}, ^id (NSError* error){
...
});
设置一个promise的值将被称为:“resolving”。你只能一次性解决一个承诺。
您可以将任何带有完成处理程序或完成委托的异步方法包装到返回承诺的方法中:
- (Promise*) fetchUserWithURL:(NSURL*)url
{
Promise* promise = [Promise new];
HTTPOperation* op = [[HTTPOperation alloc] initWithRequest:request
success:^(NSData* data){
[promise fulfillWithValue:data];
}
failure:^(NSError* error){
[promise rejectWithReason:error];
}];
[op start];
return promise;
}
任务完成后,promise 可以“履行”并传递结果值,也可以“拒绝”传递原因(错误)。
根据实际的实现,一个 Promise 也可以被取消。比如说,你持有一个请求操作的引用:
self.fetchUserPromise = [self fetchUsersWithURL:url];
您可以按如下方式取消异步任务:
- (void) viewWillDisappear:(BOOL)animate {
[super viewWillDisappear:animate];
[self.fetchUserPromise cancel];
self.fetchUserPromise = nil;
}
为了取消关联的异步任务,在包装器中注册一个失败处理程序:
- (Promise*) fetchUserWithURL:(NSURL*)url
{
Promise* promise = [Promise new];
HTTPOperation* op = ...
[op start];
promise.then(nil, ^id(NSError* error){
if (promise.isCancelled) {
[op cancel];
}
return nil; // result unused
});
return promise;
}
注意:您可以根据需要注册成功或失败处理程序,时间、地点和数量。
因此,您可以使用 Promise 做很多事情 - 甚至比这个简短的介绍更多。如果你读到这里,你可能会知道如何解决你的实际问题。它就在那里 - 只需几行代码。
我承认,对 Promise 的简短介绍非常粗略,而且对于 Objective-C 开发人员来说也很新,而且听起来可能并不常见。
您可以在 JS 社区中阅读很多关于 Promise 的内容。 Objective-C 中有一到三个实现。实际实现不会超过几百行代码。碰巧,我是其中之一的作者:
RXPromise.
持保留态度,我可能完全有偏见,显然所有其他人也曾处理过 Promise。 ;)