关于使用完成块排队

时间:2012-12-29 18:02:03

标签: ios core-data asynchronous thread-safety objective-c-blocks

因此,在了解完成阻止一段时间后,我喜欢使用完成块。我喜欢封闭,我喜欢能够随心所欲地传递任何东西。

作为线程编程的新手,我一直远离GCD和NSOperation - 但最近我不得不将异步更新编程到Core Data的东西,我开始对我的“所有完成块”产生怀疑一直“接近。”

所以这里是我自我质疑的一个例子:我有一系列可能相当大的数据(图像,声音,视频,你有什么)上传到某个服务器。这些数据的元数据存储在Core Data中,我有一个时间戳,用于决定应该上传哪些对象。所有这些上传都应该按顺序完成。

我编码的内容基本上只是一个带有完成块的函数,它在块的末尾有一个自己的调用,如下所示:

(void)uploadAllAsynchronously {
  ... // First figure out what to upload based on core data
  // Here comes the completion block in question
  void(^blk)(BOOL) = ^(BOOL)uploadSuccess {
    ... // if upload successful, update core data to mark what has been uploaded
    [self uploadAllAsynchronously]; // Recursively calls the function that contains this block.  I actually have a weak self, or if that fails to break a retain cycle, I should be able to pass in a NSManagedObjectContext as an argument.
  }
  [NSURLConnection sendAsynchronousRequest:... queue:... completionHandler:blk];


}

这应该有用,对吗?这里有什么东西是完全危险的,这表明我必须使用GCD并处理我自己的队列吗?我问这个是因为我现在遇到了一些麻烦,可能因为异步调用会导致不同的线程无法正确更新,但不确定我的代码的哪一部分是罪魁祸首。

提前致谢。

2 个答案:

答案 0 :(得分:1)

是的,这段代码应该可以工作..注意:我重命名方法然后.. uploadIfNeeded可能 - 因为它并不总是盲目上传东西......

答案 1 :(得分:1)

您的区块类型错误。

作为<{p}的the documentation

+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse *, NSData *, NSError *))handler

显示,完成黑色的类型是

void (^) (NSURLResponse *, NSData *, NSError *)

不是

void (^) (BOOL)

您应该将blk更改为

void (^blk) (NSURLResponse *, NSData *, NSError *) = ^ (NSURLResponse *response, NSData *data, NSError *error) {
    //...
};

编写

会更时尚
[NSURLConnection sendAsynchronousRequest:theRequest queue:theQueue completionHandler:^ (NSURLResponse *response, NSData *data, NSError *error) {
    //...
}];

将完成块与方法一致。


关于在完成处理程序中对NSManagedObjectContext执行操作的问题:只要传递给NSOperationQueue的{​​{1}}与管理的sendAsynchronousRequest:queue:completionHandler:相同,这就没问题创建对象上下文。但是the documentation for NSManagedObjectContext状态

  

Core Data使用线程(或序列化队列)限制来保护托管对象和托管对象上下文(请参阅“与Core Data并发”)。这样做的结果是上下文假定默认所有者是分配它的线程或队列 - 这由调用其init方法的线程确定。因此,您不应该在一个线程上初始化上下文,然后将其传递给另一个线程。相反,您应该传递对持久性存储协调器的引用,并让接收线程/队列创建从该派生协调器派生的新上下文。

如果您传递的队列不是您创建托管对象上下文的队列,则必须执行以下操作之一

  1. 在创建托管对象上下文的队列上调用-[NSOperationQueue addOperation:]

  2. 在正在进行核心数据操作的队列上创建第二个托管对象上下文(具有相同的持久性存储协调器)。

  3. 在正在进行核心数据操作的队列上创建第二个托管对象上下文和第二个持久性存储协调器。

  4. 使用锁定。

  5. Concurrency with Core Data上的文档清楚地表明您必须使用线程限制(上面的选项1-3)或使用锁定(上面的选项4)。

    这是文档关于使用锁的说法:

      

    如果您选择不使用线程包含模式 - 也就是说,如果您尝试在线程之间传递托管对象或上下文,等等 - 您必须非常小心锁定,因此您可能会否定您可能从多线程中获得的任何好处。

    这就是文档不仅要说明每个线程的托管对象上下文,还要考虑每个线程持久性存储协调器:

      

    这种方法以更高的复杂性为代价提供更高的并发性(特别是如果您需要在不同的上下文之间进行通信)并增加内存使用量。