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

假设我正在编写一个聊天应用程序:

  • 启动应用程序
  • 询问服务器(AFHTTPRequestOperation)给我所有新消息的列表
  • 循环浏览这些消息以查看是否需要下载任何图像
  • 如果是,则再次调用服务器(AFImageRequestOperation)以获取图像

  • 我不断崩溃,导致我的托管对象“消息”不再处于同一上下文中,但是我只使用了一个managedObjectContext

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

    要注意的一件事是,当我更改视图控制器时似乎会发生这种情况。我启动该应用程序,并在根视图控制器(RVC)处执行上面的步骤2。如果我在所有图像下载完成之前触摸去“MessageListViewController”(MLVC),则那些未完成下载的图像的关联消息突然变为nil 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];
    

    最佳答案

    您需要确保执行上下文(在该上下文中调用与托管对象上下文关联的方法)与托管对象上下文是适当的(即相同)。

    也就是说,当您调用

     [[DataController sharedController] saveContext];
    

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

    在这种情况下,我们可以立即得出结论,这仅适用于IFF a)AFN的完成处理程序将在主线程上执行,并且b)托管对象上下文也与主线程相关联,或者您要注意在saveContext的实现中使用此方法,并使用performBlock:performBlockAndWait:

    否则,由于受管对象上下文的执行上下文是 private 的,因此任何完成处理程序的执行上下文都不会与此匹配。因此,您违反了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的托管对象上下文已与主线程关联并且上述语句也将在主线程上执行。如果上下文使用 private 队列,除非您使用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中“获取”该对象:
    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);
        }
    }];
    

    09-30 23:45