使用Core Data在iPhone上导入大型数据集

时间:2010-01-26 16:28:32

标签: iphone core-data import

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

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

我根据文章“有效导入数据”http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CoreData/Articles/cdImporting.html和其他相关文档使用了所有推荐的技术,但它仍然非常慢。

解决方案我正在寻找的是让app暂停,但让导入后面运行(更好的一个)或防止尝试暂停应用程序。或者也欢迎任何更好的主意。

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

9 个答案:

答案 0 :(得分:4)

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

  1. 无需在手机上导入
  2. 更紧凑
  3. 如果您始终替换整个内容,只需覆盖设备中的持久存储即可。否则,您可能希望将数组维护为包含已下载的所有sqlite的plist,然后使用此数据将所有存储添加到persistentStoreCoordinator。

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

    您可以使用iPhone模拟器创建这些CoreData-SQLite商店或使用独立的Mac应用程序。您需要自己编写这两个。

答案 1 :(得分:4)

首先,如果您可以使用理想的应用程序打包数据。

然而,假设你不能这样做,那么我会这样做:

  1. 下载数据后,将其分成多个文件,然后再导入。
  2. 导入后台线程,一次导入一个文件。
  3. 导入并保存文件后,删除导入文件。
  4. 启动时,查找等待处理的文件并从中断处继续。
  5. 理想情况下,使用应用程序发送数据的工作要少得多,但第二种解决方案可行,您可以在开发过程中微调数据分解。

答案 2 :(得分: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];
}

这种方式有点粗糙,如果用户发生了以前停止的话,它会尝试从它离开的位置初始化数据存储......

答案 3 :(得分:1)

我在将很多对象导入CoreData时遇到了类似的问题。最初我在我希望创建的每个对象之后对托管对象上下文执行save&amp;插入

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

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

如果这仍然太冗长,只需编写一个darn任务并阻止用户交互,直到完成

答案 4 :(得分:0)

有没有办法可以提前打包数据 - 比如开发期间?当你将应用程序推送到商店时,一些数据已经存在?那会减少你必须提取的数据量,从而有助于解决这个问题吗?

如果数据是时间敏感的,或者没有准备好,或者由于某种原因你不能这样做,你可以在通过网络发送数据之前使用zlib压缩来压缩数据吗?

或者是手机死了做5K +插入的问题吗?

答案 5 :(得分:0)

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

您是否也在单独的(非事件/ ui)线程中处理数据?

答案 6 :(得分:0)

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

REST的优点在于它是一种内置的方式,您可以获取批量信息,这样您就不需要随意弄清楚如何分解您想要输入的文件。您只需设置要返回的记录数,并在下一个请求中跳过该记录。我不知道这对你来说是否是一个选项,所以我现在不会进入很多代码示例,但如果有可能,它可能是一种处理它的平滑方式。

答案 7 :(得分:0)

让我们接受Restful(延迟加载)不是一个选项......我知道你想要复制。如果加载问题的类型'越来越少的行加载越来越多的时间),那么在伪代码中......

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

这应该告诉你很多。

答案 8 :(得分:0)

我处理的应用程序经常需要使用Core Data处理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];
}

根据您的情况,我认为您不需要撤消管理员。拥有一个将导致性能损失,因为Core Data正在跟踪您的更改。

使用此上下文在-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)通过使用这种多线程方法,您可以同时处理您的数据。因此,创建您的NSOperation子类的3-4个实例,每个实例仅处理一部分数据,以便它们全部并发运行,从而导致解析数据集所消耗的实时数量较少。