我有一个 NSOperationQueue 处理循环中从 Web 服务器导入数据。它通过以下设计实现了这一点。
NSURLConnect 封装在一个 NSOperation 中并添加到队列中
成功完成下载后(使用块),来自请求的数据被包装在另一个 NSOperation 中,该 NSOperation 将相关数据添加到 Core Data。此操作被添加到队列中。
成功完成后(使用另一个块),(并在指定的延迟之后)我调用启动它的方法并返回到步骤 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 的示例,第二个是生成完成块的便捷方法。
注意完成块生成器被几十个不同的请求调用,类似于第一种方法。
- (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];
};
}