UIManagedDocument在后台线程中插入对象

5
这是我在Stack Overflow上的第一个问题,如果我违反了任何礼节,请见谅。我对Objective-C/应用程序开发也比较新。
我一直在跟随CS193P斯坦福课程,特别是CoreData演讲/演示。在Paul Hegarty的Photomania应用程序中,他从表视图开始,并在后台填充数据,而不会对UI流程造成任何干扰。我创建了一个应用程序,列出本地区域的企业(来自返回JSON数据的API)。
我已经按照Paul的照片/摄影师类别创建了分类。类本身的创建不是问题,问题在于它们被创建的位置。
A simplified data structure:
- Section
    - Sub-section
        - business
        - business
        - business
    - business
    - business
    - business

我的应用程序从一个UIViewController开始,其中包含几个按钮,每个按钮都打开对应部分的tableview(这些都很正常,我试图提供足够的信息,以便我的问题有意义)。 我调用一个辅助方法来创建/打开URL以用于UIManagedDocument,该文档基于这个问题。 这在应用程序运行时立即调用,并且加载速度快。
我有一个非常类似于Paul的fetchFlickrDataIntoDocument的方法:
-(void)refreshBusinessesInDocument:(UIManagedDocument *)document
{
dispatch_queue_t refreshBusinessQ = dispatch_queue_create("Refresh Business Listing", NULL);
dispatch_async(refreshBusinessQ, ^{
    // Get latest business listing
    myFunctions *myFunctions = [[myFunctions alloc] init];
    NSArray *businesses = [myFunctions arrayOfBusinesses];

    // Run IN document's thread
    [document.managedObjectContext performBlock:^{

        // Loop through new businesses and insert
        for (NSDictionary *businessData in businesses) {
            [Business businessWithJSONInfo:businessData inManageObjectContext:document.managedObjectContext];
        }

        // Explicitly save the document.
        [document saveToURL:document.fileURL 
           forSaveOperation:UIDocumentSaveForOverwriting
          completionHandler:^(BOOL success){
              if (!success) {
                  NSLog(@"Document save failed");
              }
          }];
        NSLog(@"Inserted Businesses");
    }];
});
dispatch_release(refreshBusinessQ);
}

[myFunctions arrayOfBusinesses]只是解析JSON数据并返回一个包含各个业务的NSArray。

我在创建商家代码的开头和结尾处加入了NSLog。每个商家都被分配一个部分,需要0.006秒来创建,并且有数百个这样的商家。插入操作最终需要约2秒钟。

辅助方法如下:

// The following typedef has been defined in the .h file
// typedef void (^completion_block_t)(UIManagedDocument *document);

@implementation ManagedDocumentHelper

+(void)openDocument:(NSString *)documentName UsingBlock:(completion_block_t)completionBlock
{
    // Get URL for document -> "<Documents directory>/<documentName>"
    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:documentName];

    // Attempt retrieval of existing document
    UIManagedDocument *doc = [managedDocumentDictionary objectForKey:documentName];

    // If no UIManagedDocument, create
    if (!doc) 
    {
        // Create with document at URL
        doc = [[UIManagedDocument alloc] initWithFileURL:url];

        // Save in managedDocumentDictionary
        [managedDocumentDictionary setObject:doc forKey:documentName];
    }

    // If the document exists on disk
    if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) 
    {
        [doc openWithCompletionHandler:^(BOOL success)
         {
             // Run completion block
             completionBlock(doc);
         } ];
    }
    else
    {
        // Save temporary document to documents directory
        [doc saveToURL:url 
      forSaveOperation:UIDocumentSaveForCreating 
     completionHandler:^(BOOL success)
         {
             // Run compeltion block
             completionBlock(doc);
         }];
    }
}

在viewDidLoad中调用它:
if (!self.lgtbDatabase) {
    [ManagedDocumentHelper openDocument:@"DefaultLGTBDatabase" UsingBlock:^(UIManagedDocument *document){
        [self useDocument:document];
    }];
}

useDocument函数只是将提供的文档设置为self.document。

我想要修改这段代码,使数据在另一个线程中插入,并且用户仍然可以点击按钮查看一个部分,而不会因为数据导入而卡住UI。

非常感谢您提供帮助。我已经花了几天时间研究这个问题,甚至在这里查找了其他类似的问题,但都没有解决。如果您需要其他信息,请告诉我!

谢谢。

编辑:

到目前为止,这个问题已经收到了一个反对票。如果有什么方法可以改进这个问题,或者有人知道我没有找到的问题,请评论说明如何或在哪里?如果您有其他原因投反对票,请让我知道,因为我不能理解这种消极情绪,而且很乐意学习如何更好地做出贡献。

2个回答

11

有几种方法可以实现这一点。

由于您正在使用UIManagedDocument,因此您可以利用NSPrivateQueueConcurrencyType来初始化新的NSManagedObjectContext并使用performBlock来执行操作。例如:

// create a context with a private queue so access happens on a separate thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// insert this context into the current context hierarchy
context.parentContext = parentContext;
// execute the block on the queue of the context
context.performBlock:^{

      // do your stuff (e.g. a long import operation)

      // save the context here
      // with parent/child contexts saving a context push the changes out of the current context
      NSError* error = nil;
      [context save:&error];
 }];

当您从上下文中保存数据时,私有上下文的数据将被推到当前上下文中。这个保存只在内存中可见,因此您需要访问主上下文(与UIDocument关联的上下文)并在那里执行保存操作(参见does-a-core-data-parent-managedobjectcontext-need-to-share-a-concurrency-type-wi)。

另一种方法(我最喜欢的方法)是创建一个NSOperation子类,在其中执行操作。例如,声明一个如下的NSOperation子类:

//.h
@interface MyOperation : NSOperation

- (id)initWithDocument:(UIManagedDocument*)document;

@end

//.m
@interface MyOperation()

@property (nonatomic, weak) UIManagedDocument *document;

@end

- (id)initWithDocument:(UIManagedDocument*)doc;
{
  if (!(self = [super init])) return nil;

  [self setDocument:doc];

  return self;
}

- (void)main
{ 
  NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
  [moc setParentContext:[[self document] managedObjectContext]];

  // do the long stuff here...

  NSError *error = nil;
  [moc save:&error];

  NSManagedObjectContext *mainMOC = [[self document] managedObjectContext];
  [mainMOC performBlock:^{
    NSError *error = nil;
    [mainMOC save:&error];
  }];

  // maybe you want to notify the main thread you have finished to import data, if you post a notification remember to deal with it in the main thread...
}

现在在主线程中,您可以将该操作提供给一个队列,如下所示:

MyOperation *op = [[MyOperation alloc] initWithDocument:[self document]];
[[self someQueue] addOperation:op];

顺带一提,在NSOperationmain方法中无法启动异步操作。当main方法完成时,与该操作相关联的代理将不会被调用。事实上你可以这样做,但需要处理运行循环或并发行为。

希望能对您有所帮助。


2
你是绝对的传奇!我使用了你的第一个解决方案,因为对我来说它似乎更容易理解。不幸的是,我不能给你应得的赞,因为我刚刚加入,但我会在可以的时候给你点赞。稍后我会研究NSOperations,因为那有点让我困惑。再次感谢你的帮助! - Pete C
2
仅供完成:不要忘记在您的(以及其他所有)操作之后告诉您的UIManagedDocument它处于脏状态。因此,通过调用[document updateChangeCount:UIDocumentChangeDone]来强制保存。这是CS193P斯坦福课程中缺少的内容。 - Florian Mielke
我不理解context.parentContext = context的上下文,“将上下文插入当前层次结构”。你真的想把上下文设置为它自己的父级吗?如果不是,那么父级应该是UIManagedDocument的MOC吗?在这种情况下,我们如何知道它使用的并发类型? - Rhubarb

0

起初我只是想留下一条评论,但我似乎没有权限。我只是想指出 UIDocument 除了更改计数之外还提供了

- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler

这个方法不应该像更新更改计数那样等待“方便的时刻”,因此不会有延迟。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接