处理后台位置更新和Core Data文件保护

时间:2013-02-26 01:29:14

标签: cocoa-touch core-data cllocationmanager

我一直在试验CLLocationManager的{​​{1}},我遇到了一些Core Data的问题。事实证明,自iOS 5.0起,Core Data默认使用startMonitoringSignificantLocationChanges。这意味着如果设置了密码,则持久存储在设备打开时不可用,直到首次输入密码为止。如果您正在使用位置更新,那么您的应用可能会在此期间启动,并且Core Data在尝试加载持久存储时会出错。

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

我知道我可以使用NSFileProtectionNone检查数据是否已解锁,并且我可以在我的应用程序委托中使用[[UIApplication sharedApplication] isProtectedDataAvailable],以便在解锁后做出适当的响应。这对我来说似乎很麻烦 - 我必须添加一些额外的检查以确保如果持久存储不可用则不会出错,一旦它变得可用就重新设置一堆东西,等等。而额外的代码并没有提供太多的好处 - 如果它在这种状态下启动,应用程序仍然无法执行任何操作。

所以我想我不确定哪种方式更合适“

  1. 切换到applicationProtectedDataWillBecomeUnavailable:
  2. 如果商店不可用,请添加额外的支票以跳过商品,并使用NSFileProtectionNone进行设置。
  3. 如果应用在后台启动(applicationProtectedDataWillBecomeUnavailable:)并且受保护的数据不可用([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground),只需拨打[[UIApplication sharedApplication] isProtectedDataAvailable] == NO)(或类似内容)即可退出该应用。一方面,这似乎是最简单的解决方案,我没有看到任何缺点。但它似乎......“错了”?我想我无法决定它是一个干净的解决方案还是只是一个懒惰的解决方案。
  4. 我还没想到别的什么?

2 个答案:

答案 0 :(得分:5)

经过一段时间的思考,我想出了一个我很满意的解决方案。使用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的内容。需要注意的一点是,如果用户从此后台状态启动应用,applicationWillEnterForegroundapplicationDidBecomeActive可能会在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;
}

答案 1 :(得分:0)

我有经验,你必须检查

[[UIApplication sharedApplication] isProtectedDataAvailable]

和流程

applicationProtectedDataWillBecomeUnavailable

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

managedObjectContext

对我不起作用。