处理后台位置更新和核心数据文件保护

我一直在尝试使用CLLocationManagerstartMonitoringSignificantLocationChanges并且遇到了有关Core Data的一些问题。 事实证明,自iOS 5.0以来,核心数据默认使用NSFileProtectionCompleteUntilFirstUserAuthentication 。 这意味着如果设置了密码,持久性存储在设备开机之前不可用,直到第一次输入密码时为止。 如果您使用的是位置更新,那么您的应用可能会在此期间启动,而Core Data在尝试加载持久存储时会出现错误。

显然切换到NSFileProtectionNone将是解决这个问题的最简单方法。 我宁愿不要 - 虽然我没有在数据库中存储任何超级敏感的东西,但是这些位置更新也不是超级重要的。

我知道我可以使用[[UIApplication sharedApplication] isProtectedDataAvailable]来检查数据是否已经解锁,并且我可以使用applicationProtectedDataWillBecomeUnavailable:在我的应用程序委托中对其进行适当响应,一旦它解锁。 这对我来说似乎很混乱 - 我必须添加一堆额外的检查,以确保如果持久性存储不可用,在一旦它变为可用状态后重新设置一堆事物等等,以确保没有任何问题。 而且额外的代码并没有提供太多的好处 - 如果应用程序在此状态下启动,该应用程序仍然无法执行任何操作。

所以我想我只是不确定哪个是更合适的方式来处理这个问题:

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

  • 经过一段时间的思考,我想出了一个我很满意的解决方案。 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."];
    }
    

    完成设置并重新启用界面。 这是大部分工作,但您还需要检查其他代码,以确保在持久存储可用之前没有任何依赖于核心数据的请求被调用。 有一点需要注意的是,如果用户从此后台状态启动applicationProtectedDataDidBecomeAvailableapplicationWillEnterForegroundapplicationDidBecomeActive可能会在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: over到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;
    }
    

    我经历过,你必须检查

    [[UIApplication sharedApplication] isProtectedDataAvailable]
    

    和过程

    applicationProtectedDataWillBecomeUnavailable
    

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

    managedObjectContext
    

    没有为我工作。

    链接地址: http://www.djcxy.com/p/69625.html

    上一篇: Dealing with background location updates and Core Data file protection

    下一篇: Typed expression parser