我有一个 NSOperationQueue,它循环处理从 Web 服务器导入数据。它通过以下设计来实现这一点。
NSURLConnect 包装在 NSOperation 中并添加到队列
成功完成下载(使用 block )后,来自请求的数据将被包装在另一个 NSOperation 中,该 NSOperation 将相关数据添加到 Core Data。该操作被添加到队列中。
成功完成后(使用另一个 block )(并且在指定的延迟之后),我调用启动这一切的方法并返回到步骤 1。因此,我在 x 秒后调用另一个服务器。
这很好用。我能够从服务器获取数据并处理后台的所有内容。因为这些只是 NSOperations,所以我可以将所有内容放在后台,并一次执行多个请求。这确实非常有效。
我目前遇到的唯一问题是,一旦操作开始,我就无法成功取消操作。
我尝试过类似以下的操作:
- (void)flushQueue
{
self.isFlushingQueue = YES;
[self.operationQueue cancelAllOperations];
[self.operationQueue waitUntilAllOperationsAreFinished];
self.isFlushingQueue = NO;
NSLog(@"successfully flushed Queue");
}
其中 self.isFlushingQueue 是一个 BOOL,我在向队列添加任何新操作之前使用它进行检查。这看起来应该行得通,但实际上行不通。有什么办法可以阻止我的弗兰肯斯坦创作吗?
编辑(已解决问题,但从不同的角度来看)
我仍然对为什么我无法取消这些操作感到困惑(我很乐意继续尝试可能的解决方案),但我对如何以稍微不同的方式解决这个问题有了深刻的了解。我决定只使用一个包含所有事件连接列表的数据结构(NSMutableDictionary),而不是完全处理取消操作并等待队列完成。像这样的事情:
self.activeConnections = [NSMutableDictionary dictionaryWithDictionary:@{
@"UpdateContacts": @YES,
@"UpdateGroups" : @YES}];
然后,在向队列添加任何操作之前,我只需询问该特定调用是打开还是关闭。我已经对此进行了测试,并且我成功地对我想要循环的每个单独的服务器请求进行了有限的控制。要关闭所有功能,我只需将所有连接设置为 @NO。
此解决方案有一些缺点(必须手动管理额外的数据结构,并且每个操作都必须在终止之前重新启动以查看它是打开还是关闭)。
编辑 - 寻求更准确的解决方案
我删除了所有不相关的代码(注意没有错误处理)。我发布了两种方法。第一个是如何创建请求 NSOperation 的示例,第二个是生成完成 block 的便捷方法。
请注意,与第一种方法类似,完成 block 生成器由数十个不同的请求调用。
- (void)updateContactsWithOptions:(NSDictionary*)options
{
//Hard coded for ease of understanding
NSString *contactsURL = @"api/url";
NSDictionary *params = @{@"sortBy" : @"LastName"};
NSMutableURLRequest *request = [self createRequestUsingURLString:contactsURL andParameters:params];
ConnectionCompleteBlock processBlock = [self blockForImportingDataToEntity:@"Contact"
usingSelector:@selector(updateContactsWithOptions:)
withOptions:options andParsingSelector:@selector(requestUsesRowsFromData:)];
BBYConnectionOperation *op = [[BBYConnectionOperation alloc] initWithURLRequest:request
andDelegate:self
andCompletionBlock:processBlock];
//This used to check using self.isFlushingQueue
if ([[self.activeConnections objectForKey:@"UpdateContacts"] isEqualToNumber:@YES]){
[self.operationQueue addOperation:op];
}
}
- (ConnectionCompleteBlock) blockForImportingDataToEntity:(NSString*)entityName usingSelector:(SEL)loopSelector withOptions:(NSDictionary*)options andParsingSelector:(SEL)parseSelector
{
return ^(BOOL success, NSData *connectionData, NSError *error){
//Pull out variables from options
BOOL doesLoop = [[options valueForKey:@"doesLoop"] boolValue];
NSTimeInterval timeInterval = [[options valueForKey:@"interval"] integerValue];
//Data processed before importing to core data
NSData *dataToImport = [self performSelector:parseSelector withObject:connectionData];
BBYImportToCoreDataOperation *importOperation = [[BBYImportToCoreDataOperation alloc] initWithData:dataToImport
andContext:self.managedObjectContext
andNameOfEntityToImport:entityName];
[importOperation setCompletionBlock:^ (BOOL success, NSError *error){
if(success){
NSLog(@"Import %@s was successful",entityName);
if(doesLoop == YES){
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:loopSelector withObject:options afterDelay:timeInterval];
});
}
}
}];
[self.operationQueue addOperation:importOperation];
};
}
最佳答案
取消 NSOperation 只是一个请求,是在 NSOperation
中设置的一个标志。由您的 NSOperation 子类来实际执行该请求并取消它的工作。然后,您需要确保为 isExecuting
和 isFinished
等设置正确的标志。您还需要以符合 KVO 的方式执行此操作。只有设置这些标志后,操作才会完成。
文档 Concurrency Programming Guide -> Configuring Operations for Concurrent Execution 中有一个示例。尽管我知道这个示例可能无法正确解释所有多线程边缘情况。示例代码 LinkedImageFetcher
中提供了另一个更复杂的示例:QRunLoopOperation
如果您认为自己正确响应了取消请求,那么您确实需要发布 NSOperation 子类代码来进一步检查问题。
关于ios - 停止 NSOperationQueue,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14780909/