在的NSOperation iOS的 - 如何处理循环和嵌套的NSOperation调用来获取图像 [英] NSOperation in iOS - How to handle loops and nested NSOperation call to get Images

查看:166
本文介绍了在的NSOperation iOS的 - 如何处理循环和嵌套的NSOperation调用来获取图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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


  1. 启动应用程序

  2. 问问服务器( AFHTT prequestOperation )给我的所有新邮件列表

  3. 循环通过这些消息,看看我是否需要下载任何图像

  4. 如果是的话,拨打另一个电话到服务器( AFImageRequestOperation )来获取图像

我不断收到崩溃在我的管理对象的消息是在同样的情况下不再,但我只使用一个 managedObjectContext

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

有一点要注意的是,似乎当我改变视图控制器的出现。我启动应用程序,并在根视图控制器(RVC),它执行上述步骤#2。如果我摸去了 MessageListViewController (MLVC)之前的所有图片都下载了,这些图像未完成下载突然的相关消息有一个零 managedObjectContext

下面是相关的code:

  AFHTT prequestOperation *操作= [AFJSONRequestOperation JSONRequestOperationWithRequest:requestImageInfoURL
     成功:^(*的NSURLRequest要求,NSHTTPURLResponse *响应,ID JSON){         *的NSDictionary = JSONresponse(*的NSDictionary)JSON;         如果([[[JSONresponse objectForKey:@状态] uppercaseString] isEqualToString:@ERROR])
         {
             对于(ID< CommsObserver>观察员_observers)
                 [观察者errorOccurred:[JSONresponse objectForKey:@消息]];
         }
         其他
         {
             如果([JSONresponse的containsKey:@convoMessages])
             {
                 NSArray的*给messageManager = [JSON objectForKey:@消息];                 的for(int i = 0; I< messageList.count;我++)
                 {
                     __block ConversationMessage *消息= [JSONUtility convoMessageFromJSON:[给messageManager objectAtIndex:我]];                     如果(!消息)
                         的NSLog(@无法创建新的消息......);
                     其他
                     {
                         message.unread = [NSNumber的numberWithBool:YES];
                         [DataController类sharedController] saveContext]                         如果(!(message.text || [message.text isEqualToString:@])及及(message.image || message.imageInfoKey))
                         {
                             * NSString的= IMAGEURL(message.image [通讯科urlStringForImageInfo:message.image]:[通讯科urlStringForImageKey:message.imageInfoKey扩展:message.imageInfoExt]);                             *的NSURLRequest = requestImageURL [的NSURLRequest requestWithURL:[NSURL URLWithString:IMAGEURL]];
                             AFImageRequestOperation * imageOperation;
                             imageOperation = [AFImageRequestOperation imageRequestOperationWithRequest:requestImageURL
                                   imageProcessingBlock:^ *的UIImage(*的UIImage图像){
                                       返回形象;
                                   }成功:^(*的NSURLRequest要求,NSHTTPURLResponse *响应的UIImage *图像){                                       如果(message.image)
                                       {
                                           的NSLog(@的imageinfo更新消息);                                           [公用设施updateImageInfo:message.image
                                                            withImage:图像
                                                            为preVIEW:YES
                                                          asThumbnail:YES
                                                         preserveSize:YES];
                                       }
                                       其他
                                       {
                                           的NSLog(@创建信息的新的imageinfo);                                           *的imageinfo的imageinfo = [实用程序createImageInfoFromImage:图像preVIEW:NO asThumbnail:NO preserveSize:YES];                                           如果(imageInfo.managedObjectContext ==无)
                                               的NSLog(@的imageinfo MOC是NIL);
                                           否则,如果(message.managedObjectContext ==无)
                                           {
                                               的NSLog(@消息MOC是NIL);                                               消息= [[DataController类sharedController] convoMessageForKey:message.key];                                               如果(!消息)
                                                   的NSLog(@消息NIL,这意味着它不是在MOC找到了);
                                               否则,如果(!message.managedObjectContext)
                                                   的NSLog(@消息,商务部还在NIL);
                                               其他
                                                   的NSLog(@的问题解决了......);
                                           }                                           如果(的imageinfo)
                                               [DataController类sharedController] associateImageInfo:的imageinfo withMessage:消息];
                                       }                                       [DataController类sharedController] saveContext]                                   }失败:^(*的NSURLRequest要求,NSHTTPURLResponse *响应,NSError *错误){
                                       的NSLog(@图片下载错误... \\ n%@,[的NSString stringWithFormat:@%@,错误]);
                                   }];                             [imageOperation开始]
                         }                         对于(ID< CommsObserver>观察员_observers)
                             [观察者newConvoMessages:@ [message.key]];
                     }
                 } // end有关给messageManager的循环             } // end如果JSONresponse         } //结束外,如果错误的语句     } //成功结束     失败:^(*的NSURLRequest要求,NSHTTPURLResponse *响应,NSError *错误ID JSON){
         的NSLog(@错误:\\ n%@,[的NSString stringWithFormat:@%@,错误]);
     }
];[操作开始]。


解决方案

您需要确保在调用关联到一个托管对象上下文方法的执行上下文是合适的(即是的相同的)作为管理对象范围内。

也就是说,当你调用

  [DataController类sharedController] saveContext]

线程(或调度队列),其中该方法保存:将被执行(最终)也必须在管理对象上下文关联到同样的。

在这里,在这种情况下,我们可以立即得出结论,认为这只会工作IFF一)AFN完成处理程序将执行主线程和B)的托管对象上下文关联到主线程,太,或者你照顾这个 saveContext 的实施中,并使用 performBlock: performBlockAndWait:

否则,因为被管理对象上下文的执行上下文的私人的,的执行上下文的任何的完成处理程序将不会匹配这一个。因此,你违反了核心数据的并发性规则。

当你发送一条消息,你需要确保的当前执行上下文的将是的正确的一个管理对象或管理对象上下文。也就是说,你需要使用 performBlock: performBlockAndWait:和包装访问成块:

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

请注意:你必须包装的所有的这些访问到任何 performBlock: performBlockAndWait:,与物业除外 OBJECTID 管理​​对象的。

OBJECTID 可以从任何线程获得。因此,这可以作为只要你有获取任何托管对象到任何上下文中的 OBJECTID


其他一些提示:

使用管理对象

您需要确保当你的使用的被管理对象(即发送消息的话),这将在其上关联到管理对象的管理对象相同的执行上下文中执行上下文。

这是为了确保你使用 performBlock: performBlockAndWait:如下:

 的NSManagedObjectContext *上下文= [[的NSManagedObjectContext页头]
    initWithConcurrencyType:NSPrivateQueueConcurrencyType];

注意:背景使用私有队列

  __块NSManagedObject * OBJ;
[背景performBlockAndWait:^ {
    OBJ = [背景objectRegisteredForID:theObjectID];
}];

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

 的NSString *名称= obj.name;

不安全,除非你知道OBJ的管理对象方面已经关联到主线程和上面的语句也将在主线程上执行。如果上下文使用的私人的队列中,这将是的从不的真正的,除非你使用 performBlock: performBlockAndWait:

安全:

  __块的NSString *名称;
[obj.managedObjectContext performBlockAndWait:^ {
    名称= obj.name;
}];

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

  NSManagedObjectID * MOID = obj.objectID; //从任何线程安全


移动的托管从一个环境到另一个对象:

您无法使用关联到上下文中的管理对象,在上下文中B.为了移动的对象为情境B,你首先需要在 OBJECTID ,然后取这个对象上下文中乙:

  NSManagedObjectID * MOID = obj.objectID*的NSManagedObjectContext = otherContext [的NSManagedObjectContext页头]
    initWithConcurrencyType:NSPrivateQueueConcurrencyType];[otherContext performBlock:^ {
    NSManagedObject * OBJ = [otherContext objectWithID:MOID];
    ...
}];


错误参数

与误差参数要仔细。

错误参数的总是的自动释放。 performBlockAndWait:不使用自动释放池内部。所以,你可以有一个__block变量的错误的外块:

  __块NSManagedObject * OBJ;
__block NSError *错误;
[背景performBlockAndWait:^ {
    OBJ = [背景existingObjectWithID:theObjectID错误:&放大器;错误]
}];
如果(OBJ ==无){
    的NSLog(@错误:%@,错误);
}

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

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

  __块NSManagedObject * OBJ;
[背景performBlock:^ {
    NSError *错误;
    OBJ = [背景existingObjectWithID:theObjectID错误:&放大器;错误]
    如果(OBJ ==无){
        的NSLog(@错误:%@,错误);
    }
}];

I need help understanding how to appropriate handle the following use case:

Say I'm writing a Chat app:

  1. Launch App
  2. Ask server (AFHTTPRequestOperation) to give me a list of all new messages
  3. Loop through those messages to see if I need to download any images
  4. If yes, then make another call to the server (AFImageRequestOperation) to get the image

I keep getting crashes where my managed object "message" is no longer in the same context, but I'm only using one managedObjectContext.

Is it because of the way I'm nesting the calls, since they are asynchronous? I am almost positive the message isn't getting deleted elsewhere, because I see it.

One thing to note is that is seems to happen when I change view controllers. I launch the app, and at the root view controller (RVC), it performs step #2 above. If I touch to go the the "MessageListViewController" (MLVC) before all of the images have downloaded, the associated messages for those images that didn't finish downloading suddenly have a nil managedObjectContext.

Below is the relevant code:

    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];

解决方案

You need to ensure that the execution context where you invoke methods associated to a managed object context is appropriate (namely, is the same) as for the managed object context.

That is, when you invoke

 [[DataController sharedController] saveContext];

the thread (or dispatch queue) where the method save: will be executed (eventually) MUST be the same where the managed object context is associated to.

Here in this case, we can immediately conclude, that this will only work IFF a) the completion handler of AFN will execute on the main thread AND b) the managed object context is associated to the main thread, too, OR you take care of this within the implementation of saveContext and use performBlock: or performBlockAndWait:.

Otherwise since the execution context of a managed object context is private, the execution context of any completion handler will never match this one. Hence, you violate the concurrency rules for Core Data.

Whenever you send a message to a managed object or a managed object context you need to ensure that the current execution context will be the correct one. That is, you need to use performBlock: or performBlockAndWait: and wrap accesses into the block:

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

}];

Note: you have to wrap all those accesses into either performBlock: or performBlockAndWait:, with the exception of property objectID of a managed object.

The objectID can be obtained from any thread. Thus this can be used to fetch any managed object into any context as long as you have the objectID.


A few other hints:

Using a Managed Object

You need to ensure that when you use a managed object (that is, send a message to it) that this will be executed on the same execution context which is associated to the managed object's managed object context.

That is, in order to ensure that, you use performBlock: or performBlockAndWait: as follows:

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

Note: context uses a private queue.

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

Assuming, the following statement will be executed on an arbitrary thread, this is unsafe:

NSString* name = obj.name;

"Unsafe", unless you know obj's managed object context has been associated to the main thread AND the above statement will also execute on the main thread. If the context uses a private queue, this will be never true unless you use performBlock: or performBlockAndWait::

Safe:

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

Obtaining the objectID is always safe from any thread:

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


"Move" a managed object from one context to another:

You cannot use a managed object associated to context A, in context B. In order to "move" that object into context B you first need the objectID and then "fetch" this object in context B:

NSManagedObjectID* moid = obj.objectID

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

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


Error Parameters

Be carefully with error parameters.

Error parameters are always autoreleased. performBlockAndWait: doesn't use an autorelease pool internally. So, you can have a __block variable error outside the block:

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

However performBlock: will use an autorelease pool internally! This has consequences:

If you use the asynchronous version performBlock:, you need to handle errors within the block:

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

这篇关于在的NSOperation iOS的 - 如何处理循环和嵌套的NSOperation调用来获取图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆