如果您NSOperation
是 AFHTTPRequestOperation 的子类,这很重要。AFHTTPRequestOperation 在方法中将NSOperation
' 属性completionBlock
用于其自身目的setCompletionBlockWithSuccess:failure
。在这种情况下,不要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 的异步方法。(是的,一个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:
。然后成功完成后,它会启动 async task 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 也可以被取消。假设您持有对请求操作的引用:
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 中有一到三个实现。实际实现不会超过几百行代码。碰巧,我是其中之一的作者:
RX 承诺。
持保留态度,我可能完全有偏见,显然所有其他人也曾处理过 Promise。;)