我面临着非常烦人的问题。我的iPhone应用程序正在从网络服务器加载它的数据。数据以plist形式发送,在解析时,需要使用CoreData将其存储到SQLite数据库。
问题是,在某些情况下,这些数据集太大(5000多条记录),导入时间太长。更多的是,当iPhone试图暂停屏幕时,Watchdog会杀死应用程序,因为它仍在处理导入并且最多不响应5秒,因此导入永远不会完成。
我根据文章“有效导入数据”http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CoreData/Articles/cdImporting.html和其他相关文档使用了所有推荐的技术,但它仍然非常慢。
解决方案我正在寻找的是让app暂停,但让导入后面运行(更好的一个)或防止尝试暂停应用程序。或者也欢迎任何更好的主意。
如何克服这些问题的任何提示都非常感谢! 感谢
答案 0 :(得分:4)
您可能希望发送准备好使用sqlite文件,而不是将plist文件推送到手机。这有很多好处:
如果您始终替换整个内容,只需覆盖设备中的持久存储即可。否则,您可能希望将数组维护为包含已下载的所有sqlite的plist,然后使用此数据将所有存储添加到persistentStoreCoordinator。
底线:使用几个预编译的sqlite文件并将它们添加到persistentStoreCoordinator。
您可以使用iPhone模拟器创建这些CoreData-SQLite商店或使用独立的Mac应用程序。您需要自己编写这两个。
答案 1 :(得分:4)
首先,如果您可以使用理想的应用程序打包数据。
然而,假设你不能这样做,那么我会这样做:
理想情况下,使用应用程序发送数据的工作要少得多,但第二种解决方案可行,您可以在开发过程中微调数据分解。
答案 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个实例,每个实例仅处理一部分数据,以便它们全部并发运行,从而导致解析数据集所消耗的实时数量较少。