1

所以,在不久前了解了完成块之后,我非常喜欢使用完成块。我喜欢封闭,我喜欢能够在任何我想要的地方传递任何东西。

作为线程编程的新手,我一直远离 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 并处理我自己的队列?我问这个是因为我现在有点麻烦,可能其中的数据会出现不同的线程由于异步调用而无法正确更新,尽管不确定我的代码的哪一部分是罪魁祸首。

提前致谢。

4

2 回答 2

1

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

于 2012-12-29T21:51:28.213 回答
1

您的块类型错误。

作为文档_

+ (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传递sendAsynchronousRequest:queue:completionHandler:的对象与创建托管对象上下文的对象相同,就可以了。但作为各州的文件NSManagedObjectContext

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

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

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

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

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

  4. 使用锁定。

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

这就是文档对使用锁的看法:

如果你选择不使用线程包含模式——也就是说,如果你试图在线程之间传递托管对象或上下文,等等——你必须非常小心锁定,结果你可能会否定你的任何好处否则可能源自多线程。

这就是文档必须说的关于不仅具有每个线程托管对象上下文而且还具有每个线程持久存储协调器的内容:

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

于 2012-12-30T01:29:43.883 回答