10

我一直在尝试使用CLLocationManager's startMonitoringSignificantLocationChanges,但在使用 Core Data 时遇到了一些问题。事实证明,从 iOS 5.0 开始,Core Data 默认使用NSFileProtectionCompleteUntilFirstUserAuthentication. 这意味着如果设置了密码,则从设备打开到首次输入密码时,持久存储不可用。如果您使用位置更新,您的应用程序可能会在此期间启动,Core Data 会在尝试加载持久存储时出错。

显然切换到NSFileProtectionNone将是解决这个问题的最简单方法。不过我不想这样做——我不会在数据库中存储任何超级敏感的东西,但这些位置更新也不是超级关键的。

我知道我可以用它[[UIApplication sharedApplication] isProtectedDataAvailable]来检查数据是否已经解锁,并且我可以applicationProtectedDataWillBecomeUnavailable:在我的应用程序委托中使用它来在它解锁后做出适当的响应。不过,这对我来说似乎很混乱——我必须添加一堆额外的检查,以确保如果持久存储不可用时不会出现任何问题,一旦它变得可用就重新设置一堆东西,等等。而且这些额外的代码并没有带来太多好处——如果应用程序在这种状态下启动,它仍然无法做任何事情。

所以我想我只是不确定哪种是更“正确”的处理方式:

  1. 切换到NSFileProtectionNone
  2. 如果商店不可用,请添加额外的检查以跳过某些内容,并在它可用applicationProtectedDataWillBecomeUnavailable:后重新设置。
  3. 如果应用程序在后台启动 ( [[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) 并且受保护的数据不可用 ( [[UIApplication sharedApplication] isProtectedDataAvailable] == NO)) 只需调用exit(0)(或类似的方法)退出应用程序。一方面,这似乎是最简单的解决方案,我并没有真正看到任何缺点。但它也似乎……“错了”?我想我无法决定这是一个干净的解决方案还是一个懒惰的解决方案。
  4. 还有什么我没有想到的?
4

2 回答 2

6

在考虑了一段时间后,我想出了一个我满意的解决方案。该选项需要考虑的一件事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;
}
于 2013-03-04T20:37:46.793 回答
0

我经历过,你必须检查

[[UIApplication sharedApplication] isProtectedDataAvailable]

并处理

applicationProtectedDataWillBecomeUnavailable

确保您不访问受保护的文件。检查

managedObjectContext

对我不起作用。

于 2015-12-07T09:39:47.413 回答