2010-01-26 62 views
5

我正面临着非常烦人的问题。我的iPhone应用程序正在从网络服务器加载它的数据。数据以plist的形式发送,解析时,它需要使用CoreData存储到SQLite数据库。使用CoreData在iPhone上导入大型数据集

问题是,在某些情况下,这些数据集太大(5000+条记录)并且导入时间过长。更多的是,当iPhone试图暂停屏幕时,看门狗杀死了应用程序,因为它仍在处理导入,并且最多不会响应5秒,因此导入永远不会结束。

根据文章“Efficiently Importing Data”http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CoreData/Articles/cdImporting.html以及其他关于此的文档,我使用了所有推荐的技术,但它仍然非常慢。

解决方案我正在寻找的是让应用程序暂停,而是让进口跑在后面的(更好的),或防止企图中止应用在所有。或者更好的主意也欢迎。

有关如何克服这些问题的任何提示都非常感谢! 感谢

+0

这个问题是怎么发生的?我正在处理类似大小的数据集(如果不大),并且需要每天从Web服务中将其拉下一次。我正在考虑下载夜间准备的.sqlite文件与实际的Web服务。 – Augie 2012-07-10 20:51:47

+2

我认为加载准备好的sqlite文件是这种情况下最好的解决方案,或者至少是最简单的一种。无论如何,其他解决方案太复杂,手机设备性能差。 – Matthes 2012-07-11 09:48:47

回答

0

有没有什么建议可以提前将数据打包任何方式 - 在开发过程中说的?当你将应用推送到商店时,一些数据已经在那里了?这将减少你必须提取的数据量,从而帮助解决这个问题?

如果数据是时间敏感的,或者没有准备好,或者出于某种原因,你不能做到这一点,您可以使用zlib压缩你船它在网络上之前压缩数据?

或者是手机死于5K +插入的问题?

+0

感谢您的快速回复。是的,问题在于它在5K +插件上死亡。数据由服务器压缩,所以下载时间不是问题。不幸的是,由于它是基于时间的更新,因此无法预加载或缓存。 – Matthes 2010-01-26 16:40:48

+0

那么,如何将插入物分成10x500个物品插入物?这实际上只做对了一次?在应用程序的首次发布? 另外,也许你只是在不同的线程中根据需要拉下部分数据。如果数据在服务器上被分割,那么您将能够更好地识别出您需要的细分,并且只提取这些细分? – 2010-01-27 01:13:21

4

而不是将plist文件推送到手机,您可能想发送准备使用sqlite文件。这样做有很多好处:

  1. 不需要输入手机
  2. 更紧凑

。如果你总是更换整个内容简单地覆盖永久存储在设备中。否则,你可能想要维护一个数组作为你所下载的所有sqlite的plist,然后使用它将所有商店添加到persistentStoreCoordinator。

底线:使用多个预编译sqlite的文件,并将它们添加到persistentStoreCoordinator。

您可以使用iPhone模拟器创建这些CoreData,SQLite的,存储或使用一个独立的Mac应用程序。你需要自己写这两个。

0

我想你并没有向客户展示所有5K记录?我建议在服务器上完成所有您需要的聚合,然后仅将必要的数据发送到手机。即使这涉及到生成几个不同的数据视图,它仍然会比发送(然后处理)iPhone中的所有行快几个数量级。

你还在单独的(非事件/ UI)线程中处理数据吗?

+0

这种数据量一次加载的原因是预缓存。即使您处于离线状态,数据仍然可用,因此该应用程序仍可用。不幸的是,这是这个应用程序的核心原则,所以没有办法改变它... – Matthes 2010-01-27 09:58:57

2

我通过将插入处理放在后台线程中解决了类似的问题。但是,首先我创建了一个进度警报,以便用户在插入条目时无法操作数据存储。

这基本上是ViewControllers viewDidLoad中

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    NSError *error = nil; 
    if (![[self fetchedResultsController] performFetch:&error]) { 
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
     abort(); 
    } 

    // Only insert those not imported, here I know it should be 2006 entries 
    if ([self tableView:nil numberOfRowsInSection:0] != 2006) { 

     // Put up an alert with a progress bar, need to implement 
     [self createProgressionAlertWithMessage:@"Initilizing database"]; 

     // Spawn the insert thread making the app still "live" so it 
     // won't be killed by the OS 
     [NSThread detachNewThreadSelector:@selector(loadInitialDatabase:) 
           toTarget:self 
         withObject:[NSNumber numberWithInt:[self tableView:nil 
               numberOfRowsInSection:0]]]; 
    } 
} 

插入线程这样

- (void)loadInitialDatabase:(NSNumber*)number 
{ 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    int done = [number intValue]+1; // How many done so far 

    // I load from a textfile (csv) but imagine you should be able to 
    // understand the process and make it work for your data 
    NSString *file = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] 
               pathForResource:@"filename" 
                 ofType:@"txt"] 
               encoding:NSUTF8StringEncoding 
                error:nil]; 

    NSArray *lines = [file componentsSeparatedByString:@"\n"]; 

    float num = [lines count]; 
    float i = 0; 
    int perc = 0; 

    for (NSString *line in lines) { 
     i += 1.0; 

     if ((int)(i/(num*0.01)) != perc) { 
      // This part updates the alert with a progress bar 
      // setProgressValue: needs to be implemented 
      [self performSelectorOnMainThread:@selector(setProgressValue:) 
            withObject:[NSNumber numberWithFloat:i/num] 
           waitUntilDone:YES]; 
      perc = (int)(i/(num*0.01)); 
     } 

     if (done < i) // keep track of how much done previously 
      [self insertFromLine:line]; // Add to data storage... 

    } 

    progressView = nil; 
    [progressAlert dismissWithClickedButtonIndex:0 animated:YES]; 
    [pool release]; 
} 

这是一个有点粗糙这种方式,它会尝试初始化从它留下的,其中数据存储完成如果用户发生以前的时间停止它...

+0

感谢您的所有答复。然而,对于我来说,没有任何提议的解决方案似乎是正确的。 首先,这是定期的数据更新,而不是在首次启动应用程序时进行一次数据库初始化。它会在一段时间内检查数据。 目前,数据以plist xml格式下载并解析。这一点没问题。 然后,数据一次导入500个左右的记录(尝试各种批量大小的值,但没有显着影响)。 这一切都是在后台完成,同时显示了旋转轮的启动屏幕。 但是,问题仍然保持不变:( – Matthes 2010-01-27 09:34:21

+0

所以我试图把数据库导入部分分开线程(我不知道,实际上它在主线程上执行),它的方式,该应用程序没有被杀死OS,当你指出切换到挂起模式时,所以这部分已经解决 - 谢谢你的提示,但是导入如此大量的数据所需的时间仍然不可接受 - 大约需要5分钟的时间完成! – Matthes 2010-01-27 12:49:03

4

首先,如果你可以打包数据与应用程序,这将是理想的。

但是,假设你不能这样做,那么我会做那么以下:

  1. 一旦数据之前进口其下载突破到多个文件
  2. 导入后台线程,一次一个文件。
  3. 一旦文件被导入并保存,删除导入文件。
  4. 启动时,查找等待处理的文件并从中断处继续。

理想情况下,与应用程序一起发送数据的工作量会少得多,但第二种解决方案可以正常工作,您可以在开发过程中对数据分解进行微调。

+0

谢谢这似乎是一个很好的解决方案,但是请记住我正在处理XML文件,所以很可能在合理的位置拆分XML(保持有效)需要另一个处理时间,这可能也会很长(处理XML上的iPhone是非常痛苦和缓慢的,正如你可能知道的那样)。所以,可能对我来说也没有选择:( – Matthes 2010-01-27 10:59:57

+0

由于数据是作为plist来的,把它拉到NSDictionary中,spl它将字典分开并将其写回。如果那是在解析所有这些数据并将其注入到Core Data中的时候,我会非常惊讶。 – 2010-01-28 09:44:54

0

您可以设置您的服务器端以暴露RESTful Web服务来处理您的数据吗?我有类似的问题,并能够通过RESTful Web服务公开我的信息。 iPhone上有一些库可以很容易地从web服务中读取数据。我选择了从服务请求JSON并使用iPhone上的SBJSON库来快速获取我得到的结果并将它们转换为词典以便于使用。我使用ASIHTTP库进行Web请求并排队跟进请求并使它们在后台运行。

关于REST的好处是,它是一种内置的方式让您获取批量信息,因此您无需任意弄清楚如何分解要输入的文件。您只需设置多少记录就可以找回,而下一个请求则会跳过那么多记录。我不知道这对你来说是否是一种选择,所以我现在不会介绍很多代码示例,但如果可能的话,它可能是一种平滑的方式来处理它。

0

让我们接受Restful(懒加载)不是一个选项......我明白你想要复制。如果负载问题类型的“少排在越来越多的时间加载),然后在伪代码...

[self sQLdropIndex(OffendingIndexName)] 
[self breathInOverIP]; 
[self breathOutToSQLLite]; 
[self sQLAddIndex(OffendingIndexName)] 

这应该告诉你很多。

1

我有一个类似的问题,导入许多对象到CoreData中。起初,我在每个对象上创建&插入后,在托管对象上下文上做了一个save

你应该做的是创建/初始化你想要保存在CoreData中的每个对象,并且在所有远程数据循环后+创建对象,然后执行托管对象上下文save

我想你可以把这看作是在SQLite数据库中做一个事务:开始事务,做大量的插入/更新,结束事务。

如果这仍然是过于冗长,只是线程织补任务,并防止用户交互,直至完全

0

我对经常必须处理100K插入,删除和更新与核心数据的应用工作。如果它在5K刀片上窒息,那么需要做一些优化。

首先,创建一些NSOperation子类来处理数据。覆盖它的-main方法来执行处理。但是,此方法不能保证在主线程上运行。事实上,它的目的是为了避免在主线程上执行代价昂贵的代码,从而导致严重冻结,从而影响用户体验。因此,在-main方法中,您需要创建另一个托管对象上下文,它是您的主线程托管对象上下文的子项。

- (void)main 
{ 
    NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [ctx setPersistentStoreCoordinator:mainManagedObjectContext.persistentStoreCoordinator]; 
    [ctx setUndoManager:nil]; 
    // Do your insertions here! 
    NSError *error = nil; 
    [ctx save:&error]; 
} 

根据你的情况,我不认为你需要一个撤销管理器。由于核心数据正在跟踪您的更改,因此将会导致性能损失。

使用THIS上下文在-main方法中执行所有CRUD操作,然后保存该托管对象上下文。无论您的主线程的托管对象上下文拥有什么,都必须注册以响应名为NSManagedObjectContextDidSaveNotification的NSNotification。注册像这样:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil]; 

然后定义选择:

- (void)mocDidSaveNotification:(NSNotification *)notification 
{ 
    NSManagedObjectContext *ctx = [notification object]; 
    if (ctx == mainManagedObjectContext) return; 
    [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; 
} 

当这一切走到一起,它可以让你执行在后台线程长时间操作而不会阻塞UI线程。这种体系结构有几种不同的形式,但其核心主题是:在BG线程上处理,在主线程上合并,更新UI。还有一些需要注意的事项:(1)在处理过程中保持一个自动释放池,并且每隔一段时间耗尽一次以减少内存消耗。在我们的例子中,我们每1000个对象就做一次。根据您的需要进行调整,但请记住,根据每个对象所需的内存量,耗尽可能会很昂贵,因此您不想经常这样做。 (2)尽量减少你的数据到绝对的最低限度,你需要有一个功能的应用程序。通过减少要解析的数据量,可以减少保存所需的时间。 (3)通过使用这种多线程方法,您可以同时处理您的数据。因此,创建3-4个NSOperation子类的实例,每个实例只处理一部分数据,以便它们全部同时运行,从而导致分析数据集所需的实时时间更少。