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

时间:2013-11-24 06:38:36

标签: ios asynchronous nsmanagedobject nsmanagedobjectcontext nsoperation

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

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

  1. 启动应用
  2. 要求服务器(AFHTTPRequestOperation)向我提供所有新消息的列表
  3. 循环显示这些消息以查看是否需要下载任何图像
  4. 如果是,则再次呼叫服务器(AFImageRequestOperation)以获取图像
  5. 我一直在崩溃,我的托管对象“消息”不再在同一个上下文中,但我只使用一个managedObjectContext

    是否因为它是嵌套调用的方式,因为它们是异步的?我几乎肯定这条消息不会被其他地方删除,因为我看到了它。

    有一点需要注意的是,当我更改视图控制器时,似乎会发生这种情况。我启动了应用程序,并在根视图控制器(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];
    

1 个答案:

答案 0 :(得分:2)

您需要确保调用与托管对象上下文关联的方法的执行上下文与托管对象上下文相匹配(即,相同)。

也就是说,当你调用

 [[DataController sharedController] saveContext];

将执行方法save:的线程(或调度队列)(最终)必须与托管对象上下文关联的位置相同。

在这种情况下,我们可以立即得出结论,这只会工作IFF a)AFN的完成处理程序将在主线程上执行AND b)托管对象上下文也与主线程相关联,或者你在saveContext的实施过程中处理此问题并使用performBlock:performBlockAndWait:

否则,由于托管对象上下文的执行上下文是私有任何完成处理程序的执行上下文将永远不会与此匹配。因此,您违反了Core Data的并发规则。

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

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

}];

注意:您必须将所有这些访问权限包装到performBlock:performBlockAndWait:中,但托管对象的属性objectID除外。

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


其他一些提示:

使用托管对象

当您使用托管对象(即向其发送消息)时,您需要确保这将在与托管对象的托管对象关联的同一执行上下文上执行上下文。

也就是说,为了确保您使用performBlock:performBlockAndWait:,如下所示:

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

注意:context使用私人队列。

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

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

NSString* name = obj.name;

“不安全”,除非您知道obj的托管对象上下文已与主线程关联,并且上述语句也将在主线程上执行。如果上下文使用私有队列,除非您使用performBlock:performBlockAndWait:,否则永远不会为真:

安全:

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

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

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

将托管对象从一个上下文“移动”到另一个上下文:

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

NSManagedObjectID* moid = obj.objectID

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

[otherContext performBlock:^{
    NSManagedObject* obj = [otherContext objectWithID: moid];
    ...
}];

错误参数

小心使用错误参数。

错误参数始终自动释放。 performBlockAndWait:内部不使用自动释放池。因此,您可以在块外部使用__block变量错误

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

但是performBlock:将在内部使用自动释放池!这有后果:

如果您使用异步版本performBlock:,则需要处理块中的错误:

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