在考虑了一段时间后,我想出了一个我满意的解决方案。该选项需要考虑的一件事exit(0)
是,如果用户需要一段时间来解锁设备,应用程序可能会不断加载、退出和重新加载。而如果你只是阻止应用程序做很多事情,它可能只需要加载一次,并且很可能会更有效。所以我决定尝试我的选项 3,看看它到底有多乱。结果比我想象的要简单。
首先,我BOOL setupComplete
向我的应用程序委托添加了一个属性。这为我提供了一种简单的方法来检查应用程序是否在各个时间点完全启动。然后application:didFinishLaunchingWithOptions:
我尝试初始化托管对象上下文,然后执行以下操作:
NSManagedObjectContext *moc = [self managedObjectContext];
if (moc) {
self.setupComplete = YES;
[self setupWithManagedObjectContext:moc];
} else {
UIApplication *app = [UIApplication sharedApplication];
if ([app applicationState] == UIApplicationStateBackground && ![app isProtectedDataAvailable]) {
[app beginIgnoringInteractionEvents];
} else [self presentErrorWithTitle:@"There was an error opening the database."];
}
setupWithManagedObjectContext:
只是一个完成设置的自定义方法。我不确定这beginIgnoringInteractionEvents
是必要的,但我添加它是为了安全起见。这样,当应用程序被带到前面时,我可以确定界面被冻结,直到设置完成。如果急切的用户焦急地敲击,它可能会避免崩溃。
然后在applicationProtectedDataDidBecomeAvailable:
我调用这样的东西:
if (!self.setupComplete) {
NSManagedObjectContext *moc = [self managedObjectContext];
if (moc) {
self.setupComplete = YES;
[self setupWithManagedObjectContext:moc];
UIApplication *app = [UIApplication sharedApplication];
if ([app isIgnoringInteractionEvents]) [app endIgnoringInteractionEvents];
} else [self presentErrorWithTitle:@"There was an error opening the database."];
}
这样就完成了设置并重新启用了界面。这是大部分工作,但您还需要检查您的其他代码,以确保在您的持久存储可用之前没有调用任何依赖 Core Data 的内容。需要注意的一件事是,如果用户从这个后台状态启动应用程序,它applicationWillEnterForeground
可能applicationDidBecomeActive
会在之前被调用。applicationProtectedDataDidBecomeAvailable
所以在不同的地方我已经添加if (self.setupComplete) { … }
以确保在它准备好之前没有任何东西运行。加载数据库后,我还有几个地方需要刷新界面。
为了(部分)在不开车的情况下进行测试,我临时修改application:didFinishLaunchingWithOptions:
为不设置数据库:
NSManagedObjectContext *moc = nil; // [self managedObjectContext];
if (moc) {
self.setupComplete = YES;
[self setupWithManagedObjectContext:moc];
} else {
UIApplication *app = [UIApplication sharedApplication];
// if ([app applicationState] == UIApplicationStateBackground && ![app isProtectedDataAvailable]) {
[app beginIgnoringInteractionEvents];
// } else [self presentErrorWithTitle:@"There was an error opening the database."];
}
然后我把我的代码applicationProtectedDataDidBecomeAvailable:
移到了applicationWillEnterForeground:
. 这样我就可以启动应用程序,确保没有意外发生,按下主页按钮,再次打开应用程序,并确保一切正常。由于实际代码需要移动很长的距离并每次等待五分钟,这给了我一个很好的方法来估计正在发生的事情。
最后一件让我绊倒的事情是我执着的商店协调员。典型的实现可能如下所示:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) return _persistentStoreCoordinator;
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Test.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
这大致基于 Apple 的示例代码,它在注释中解释了您需要适当地处理错误。我自己的代码比这多一点,但我没有考虑过的一件事是,如果加载持久存储时出错,这将返回一个非零结果!这允许我所有其他代码继续运行,就好像它工作正常一样。即使persistentStoreCoordinator 被再次调用,它也只会返回相同的协调器,没有有效的存储,而不是尝试再次加载存储。有多种方法可以解决这个问题,但对我来说,最好不要设置 _persistentStoreCoordinator,除非它能够添加商店:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) return _persistentStoreCoordinator;
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Test.sqlite"];
NSError *error = nil;
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if ([coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
_persistentStoreCoordinator = coordinator;
} else {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}
return _persistentStoreCoordinator;
}