iOS中的NSOperation – 如何处理循环和嵌套的NSOperation调用来获取图像

我需要帮助了解如何处理以下用例:

说我正在写一个聊天应用程序:

  1. 启动应用程序
  2. 询问服务器( AFHTTPRequestOperation )给我一个所有新消息的列表
  3. 通过这些消息循环,看看我是否需要下载任何图像
  4. 如果是的话,再次调用服务器( AFImageRequestOperation )来获取图像

我不断收到我的托pipe对象“消息”不再在同一个上下文中的崩溃,但我只使用一个managedObjectContext

是因为我嵌套的调用方式,因为它们是asynchronous的? 我几乎积极的消息不会被删除在其他地方,因为我看到它。

有一点要注意的是,当我改变视图控制器似乎发生。 我启动应用程序,并在根视图控制器(RVC)上执行上面的步骤#2。 如果在下载所有图像之前触摸了“ MessageListViewController ”(MLVC),则那些没有完成下载的图像的关联消息突然没有managedObjectContext

以下是相关的代码:

  AFHTTPRequestOperation * operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:requestImageInfoURL success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) { NSDictionary * JSONresponse = (NSDictionary *)JSON; if( [[[JSONresponse objectForKey:@"status"] uppercaseString] isEqualToString:@"ERROR"] ) { for( id<CommsObserver> observer in _observers ) [observer errorOccurred:[JSONresponse objectForKey:@"message"]]; } else { if( [JSONresponse containsKey:@"convoMessages"] ) { NSArray * messageList = [JSON objectForKey:@"messages"]; for( int i = 0 ; i < messageList.count ; i++ ) { __block ConversationMessage * message = [JSONUtility convoMessageFromJSON:[messageList objectAtIndex:i]]; if( !message ) NSLog( @"Couldn't create the new message..." ); else { message.unread = [NSNumber numberWithBool:YES]; [[DataController sharedController] saveContext]; if( (!message.text || [message.text isEqualToString:@""]) && (message.image || message.imageInfoKey) ) { NSString * imageURL = (message.image ? [Comms urlStringForImageInfo:message.image] : [Comms urlStringForImageKey:message.imageInfoKey extension:message.imageInfoExt]); NSURLRequest *requestImageURL = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURL]]; AFImageRequestOperation * imageOperation; imageOperation = [AFImageRequestOperation imageRequestOperationWithRequest:requestImageURL imageProcessingBlock:^UIImage *(UIImage *image) { return image; } success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) { if( message.image ) { NSLog( @"updating imageInfo for message" ); [Utilities updateImageInfo:message.image withImage:image asPreview:YES asThumbnail:YES preserveSize:YES]; } else { NSLog( @"creating a new imageInfo for message" ); ImageInfo * imageInfo = [Utilities createImageInfoFromImage:image asPreview:NO asThumbnail:NO preserveSize:YES]; if( imageInfo.managedObjectContext == nil ) NSLog( @"imageInfo MOC is NIL" ); else if( message.managedObjectContext == nil ) { NSLog( @"message MOC is NIL" ); message = [[DataController sharedController] convoMessageForKey:message.key]; if( !message ) NSLog( @"message is NIL, meaning it wasn't found in the MOC" ); else if( !message.managedObjectContext ) NSLog( @"message MOC was STILL NIL" ); else NSLog( @"problem solved..." ); } if( imageInfo ) [[DataController sharedController] associateImageInfo:imageInfo withMessage:message]; } [[DataController sharedController] saveContext]; } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { NSLog( @"Image DOWNLOAD error... \n%@" , [NSString stringWithFormat:@"%@" , error] ); }]; [imageOperation start]; } for( id<CommsObserver> observer in _observers ) [observer newConvoMessages:@[message.key]]; } } // End for loop of messageList } // End if JSONresponse } // End outer if ERROR statement } // End Success failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { NSLog( @"Error: \n%@" , [NSString stringWithFormat:@"%@" , error] ); } ]; [operation start]; 

您需要确保调用与托pipe对象上下文关联的方法的执行上下文与托pipe对象上下文相适合(即相同 )。

也就是说,当你调用

  [[DataController sharedController] saveContext]; 

执行(最终)方法save:的线程(或分派队列)必须与被pipe对象上下文关联的地方相同。

在这种情况下,我们可以立即得出结论:这只能工作在IFF a)AFN的完成处理程序将在主线程上执行AND b)pipe理对象上下文也与主线程相关联,或者你负责这在执行saveContext和使用performBlock:performBlockAndWait: saveContext

否则,由于托pipe对象上下文的执行上下文是私有的 ,所以任何完成处理器的执行上下文都不会匹配这个上下文。 因此,您违反了Core Data的并发规则。

无论何时向托pipe对象或托pipe对象上下文发送消息,都需要确保当前的执行上下文将是正确的 。 也就是说,您需要使用performBlock:或者performBlockAndWait:并将访问包装到块中:

 [[DataController sharedController].managedObjectContext performBlock:^{ assert(message.managedObjectContext == [DataController sharedController].managedObjectContext); message.unread = [NSNumber numberWithBool:YES]; [[DataController sharedController] saveContext]; ... }]; 

注意:除了托pipe对象的属性objectID之外,您必须将所有这些访问包装到performBlock:performBlockAndWait:

objectID可以从任何线程获得。 因此,只要您有objectID就可以使用它将任何托pipe对象提取到任何上下文中。


其他一些提示:

使用托pipe对象

您需要确保在使用托pipe对象(即向其发送消息)时,将在与托pipe对象的托pipe对象上下文关联的相同执行上下文中执行此操作。

也就是说,为了确保你使用performBlock:或者performBlockAndWait:如下:

 NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 

注意: context使用私有队列。

 __block NSManagedObject* obj; [context performBlockAndWait:^{ obj = [context objectRegisteredForID:theObjectID]; }]; 

假设,以下语句将在任意线程上执行,这是不安全的:

 NSString* name = obj.name; 

“不安全”,除非你知道obj的托pipe对象上下文已经被关联到主线程上面的语句也会在主线程上执行。 如果上下文使用私有队列,那么除非使用performBlock:performBlockAndWait: ::

安全:

 __block NSString* name; [obj.managedObjectContext performBlockAndWait:^{ name = obj.name; }]; 

从任何线程获取objectID始终是安全的:

 NSManagedObjectID* moid = obj.objectID; // safe from any thread 

将pipe理对象从一个上下文移动到另一个上下文:

您不能在上下文B中使用与上下文A关联的托pipe对象。为了将该对象“移动”到上下文B中,首先需要objectID ,然后在上下文B中“获取”此对象:

 NSManagedObjectID* moid = obj.objectID NSManagedObjectContext* otherContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [otherContext performBlock:^{ NSManagedObject* obj = [otherContext objectWithID: moid]; ... }]; 

错误参数

慎重处理错误参数。

错误参数始终是自动发布的。 performBlockAndWait:内部不使用自动释放池。 所以,你可以在块外面有一个__blockvariables错误

 __block NSManagedObject* obj; __block NSError* error; [context performBlockAndWait:^{ obj = [context existingObjectWithID:theObjectID error:&error]; }]; if (obj==nil) { NSLog(@"Error:%@", error); } 

然而performBlock:将在内部使用一个autorelease池! 这有后果:

如果使用asynchronous版本performBlock: ,则需要处理该块的错误:

 __block NSManagedObject* obj; [context performBlock:^{ NSError* error; obj = [context existingObjectWithID:theObjectID error:&error]; if (obj==nil) { NSLog(@"Error:%@", error); } }];