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

Cocoa touch 处理后台位置更新和核心数据文件保护,cocoa-touch,core-data,cllocationmanager,Cocoa Touch,Core Data,Cllocationmanager,我一直在试验CLLocationManager的startMonitoringSignificantLocationChanges,我在核心数据方面遇到了一些问题。事实证明,自iOS 5.0以来,核心数据默认使用nsfileprotectioncompleteentilfirstureauthentication。这意味着,如果设置了密码,则从设备打开到首次输入密码,永久存储区都不可用。如果您使用的是位置更新,那么您的应用程序可能会在这段时间内启动,而核心数据在尝试加载永久存储时会出错 显然,切

我一直在试验
CLLocationManager
startMonitoringSignificantLocationChanges
,我在核心数据方面遇到了一些问题。事实证明,自iOS 5.0以来,核心数据默认使用
nsfileprotectioncompleteentilfirstureauthentication
。这意味着,如果设置了密码,则从设备打开到首次输入密码,永久存储区都不可用。如果您使用的是位置更新,那么您的应用程序可能会在这段时间内启动,而核心数据在尝试加载永久存储时会出错

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

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

因此,我想我只是不确定哪种方法更“合适”:

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

  • 经过一段时间的思考,我想出了一个我很满意的解决方案。一个需要考虑的是<代码>退出(0)选项,即如果用户需要一段时间来解锁设备,应用程序可以持续加载、退出和重新加载。然而,如果你只是简单地阻止应用程序做很多事情,它可能只需要加载一次,而且很可能会更有效率。所以我决定试试我的选项3,看看它到底有多混乱。结果比我想象的要简单

    首先,我向我的应用程序委托添加了一个
    boolsetupcomplete
    属性。这给了我一个简单的方法来检查应用程序是否在不同的时间点完全启动。然后在
    应用程序中: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."];
    }
    
    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."];
    }
    
    - (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;
    }
    
    setupWithManagedObjectContext:
    只是一个完成设置的自定义方法。我不确定
    beginIgnoringInteractionEvents
    是否有必要,但为了安全起见,我添加了它。这样,当应用程序被带到前端时,我可以确保界面被冻结,直到安装完成。如果热切的用户正在焦急地点击,它可能会避免崩溃

    然后在
    applicationProtectedDataDidBecomeAvailable:

    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."];
    }
    
    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."];
    }
    
    - (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;
    }
    
    这将完成设置并重新启用界面。这是大部分工作,但您还需要检查其他代码,以确保在持久存储可用之前,不会调用任何依赖于核心数据的代码。需要注意的一点是,如果用户从该后台状态启动应用程序,
    application将进入前台
    ,并且在
    applicationprotecteddata变得可用之前,
    applicationdidebomeactive
    可能会被调用。因此,我在不同的地方添加了
    if(self.setupComplete){…}
    ,以确保在准备就绪之前不会运行任何东西。我还有几个地方需要在数据库加载后刷新界面

    为了(部分地)测试这一点而不需要大量开车,我临时修改了
    应用程序:didfishlaunchingwithoptions:
    以不设置数据库:

    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."];
    }
    
    然后,我将代码移到
    applicationprotecteddata中,使其变得可用:
    转到
    applicationWillEnterForeground:
    。这样我就可以启动应用程序,确保没有意外发生,按下home按钮,再次打开应用程序,确保一切正常。由于实际的代码需要移动一段相当长的距离,每次需要等待五分钟,因此这为我提供了一个很好的方法来估计发生了什么

    最后一件让我绊倒的事是我坚持不懈的商店协调员。典型的实现可能如下所示:

    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."];
    }
    
    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."];
    }
    
    - (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;
    }
    
    这大致基于苹果的示例代码,该示例代码在注释中解释了您需要适当处理错误。我自己的代码做的比这稍微多一些,但有一件事我没有考虑到,如果加载持久存储时出错,这将返回一个非nil的结果!这允许我的所有其他代码继续运行,就好像它工作正常一样。即使再次调用persistentStoreCoordinator,它也只会返回相同的协调器,没有有效的存储,而不会再次尝试加载存储。有多种方法可以处理此问题,但在我看来,最好不要设置_persistentstorecordinator,除非它能够添加存储:

    - (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;
    }
    

    我经历过,你