2016-01-06 18 views
0

我正在研究一个连接到Web服务的应用程序,该应用程序在启动期间检索大量数据。我使用并发来避免UI阻塞。我选择了以下核心数据堆栈模式:后台私有moc - >主要moc - >编写器私有moc - >协调器 - >文件。核心数据并发性导入性能

导入操作时会出现问题。 CPU被100%使用,应用程序在整个过程中变得缓慢。我使用300个对象的批次进行处理,共导入约10,000个对象。

对于每个批处理,都会创建一个NSOperation,并使用关联的临时moc作为后台子节点。操作在NSOperationQueue中排队。 导入作业完成后,应用程序变得更慢,具体取决于正在运行的作业数量。我还注意到,当应用程序被杀害,并重新启动,它的方式更加可用和快速。

导入时,我的内存占用量在40Mo和60Mo之间变化。你觉得这太过分了吗?

你认为我的堆栈模式适合我的需求吗?我应该迁移到2个协调员的堆栈吗?

此外,当获取数据以显示在tableView中时,我应该使用performBlockAndWait在显示视图之前立即获取数据吗?

感谢您的帮助

+0

你能发表一些代码吗? – lukaivicev

+0

从来没有一位作家会通过主要的moc去找协调员。当您保存堆栈时,您将失去所有性能提升。 – Avi

+0

感谢您的回答。所以我应该设置作家上下文的背景上下文子节点,并让主要上下文直接链接到协调器?但根据@MattMorey的说法,作者应该是主https://speakerdeck.com/player/360691a030570131a0a76af09d9fc329#的父亲? –

回答

1

您所描述的堆栈是好的。

CPU使用率可能会引起误解。你想确保你没有在主线程中,因为这会导致你的应用程序的大部分缓慢和/或口吃。

当您在乐器中观看您的应用程序时,最需要的是什么?在主队列上花费多少时间?

通常,导入不应导致CPU占用100%。如果您是通过后台线程完成这项工作的话,那么很可能会取得一些性能提升。

如果应该分享您的导入代码和/或仪器跟踪,以便我可以看到发生了什么。

+0

我会尽快给你从仪器更多的细节。感谢您的帮助。 –

0

我认为你的设置有问题。您声明后台托管对象上下文的子项是主线程,并且您创建要导入的子项。这势必会导致UI故障。

此外,我相信依靠NSOperation是不必要的过度工程。您应该使用NSManagedObjectContext块API。

我推荐的设置是:

RootContext (background, writing to persistent store) -> parent of 
MainContext (foreground, UI) -> parent of 
WorkerContext(s) (background, created and discarded ad hoc) 

您可以在网页的回调创建工人上下文调用做进口繁重。确保您正在使用块API并将所有对象限制在本地上下文中。您需要将上下文推送到主线程(它可以在数据保存到存储之前开始显示数据),并定期保存主上下文和写入程序上下文,并始终使用bock API。

典型的这种saveContext功能,可以调用线程安全的(这里self指的是数据管理的单身或应用程序的委托):

func saveContext() { 
    if self.managedObjectContext.hasChanges { 
     self.managedObjectContext.performBlocAndWait { 
      do { try self.managedObjectContext.save() } 
      catch let error as NSError { 
       print("Unresolved error while saving main context \(error), \(error.userInfo)") 
      } 
     } 
     self.rootContext.performBlockAndWait { 
      do { try self.rootContext.save() } 
      catch let error as NSError { 
       print("Unresolved error while saving to persistent store \(error), \(error.userInfo)") 
      } 
     } 
    } 
} 
+0

我的堆栈与您所介绍的堆栈完全相同。唯一的区别是,我使用工作者作为现有背景环境的子代(主要子代)。另一方面,我会尝试删除NSOperation进口原因,我认为你是正确的过度工程。我也为所有与核心数据相关的工作使用了块API,但我并不喜欢performBlockAndWait用于保存方法,因为我认为阻止调用线程对于那项工作来说不是必需的,不是吗? –

0

经过测试和仪器跟踪的几天里,我可以给你更多细节。下面的代码片段显示了如何拯救我的情况下(基于父/子图案)从一个共享实例:

- (void)save { 
    [self.backgroundManagedObjectContext performBlockAndWait:^{ 
     [self saveContext:self.backgroundManagedObjectContext]; 

     [self.mainManagedObjectContext performBlock:^{ 
      [self saveContext:self.mainManagedObjectContext]; 

      [self.writerManagedObjectContext performBlock:^{ 
       [self saveContext:self.writerManagedObjectContext]; 
      }]; 
     }]; 
    }]; 
} 

- (void)saveContext:(NSManagedObjectContext*)context { 
    NSError *error = nil; 

    if ([context hasChanges] && ![context save:&error]) { 
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
    } 
} 

然后每次导入工作是在后台进行的上下文感谢的同步操作。以下方法在操作主体中被触发。

- (void)operationDidStart 
{ 
    NSManagedObjectContext *moc = self.context; 
    NSMutableArray *insertedOrUpdatedObjects = [NSMutableArray array]; 
    NSMutableArray *subJSONs = [NSMutableArray array]; 
    NSUInteger numberOfJobs = ceil((double)self.JSONToImport.count/self.batchSize); 

    for (int i = 0; i < numberOfJobs; i++) { 
     NSUInteger startIndex = i * self.batchSize; 
     NSUInteger count = MIN(self.JSONToImport.count - startIndex, self.batchSize); 
     NSArray *arrayRange = [self.JSONToImport subarrayWithRange:NSMakeRange(startIndex, count)]; 
     [subJSONs addObject:arrayRange]; 
    } 

    __block NSUInteger numberOfEndedJobs = 0; 

    for (NSArray *subJSON in subJSONs) { 
     [moc performBlock:^{ 
      [self startJobWithJSON:subJSON context:moc completion:^(NSArray *importedObjects, NSError *error) { 

       numberOfEndedJobs++; 

       if (!error && importedObjects && importedObjects.count > 0) { 
        [insertedOrUpdatedObjects addObjectsFromArray:importedObjects]; 
       } 

       if (numberOfEndedJobs == numberOfJobs) { 
        [[CoreDataManager manager] save]; 

        if (self.operationCompletion) { 
         self.operationCompletion(self, insertedOrUpdatedObjects, error); 
        } 
       } 
      }]; 
     }]; 
    } 
} 

正如你所看到的,我将我的批量导入分批(500)。该操作在后台上下文队列上执行每个批次,并在所有批次结束时保存我的堆栈。

由于使用了时间事件探查器,似乎这个保存方法占用了每个线程23%的CPU使用量。

希望尽可能清楚。