Work with background location updates and Core Data file protection

I experimented with CLLocationManager startMonitoringSignificantLocationChanges and I ran into some problems with Core Data. It turns out that with iOS 5.0 Core Data uses NSFileProtectionCompleteUntilFirstUserAuthentication by default. This means that if an access code is set, permanent storage is not available from the moment the device is turned on until the password is entered first. If you use location updates, your application may be running during this time, and Core Data will receive an error when trying to load persistent storage.

Obviously, switching to NSFileProtectionNone would be the easiest way to solve this problem. I would rather not do this - I do not store anything supersensitive in the database, but these location updates are also not too critical.

I know that I can use [[UIApplication sharedApplication] isProtectedDataAvailable] to check if the data has been unlocked yet, and I can use applicationProtectedDataWillBecomeUnavailable: in my delegate to respond accordingly after unlocking it. This seems dirty to me, although I have to add a few extra checks to make sure that nothing happens if the permanent storage is unavailable, reinstall a bunch of things when it becomes available, and so on. And this additional code does not bring much benefit - the application will still not be able to do anything if it starts in this state.

So, I think I'm just not sure if this is the more β€œcorrect” way to handle this:

  • Switch to NSFileProtectionNone .
  • Add additional checks to skip something if the store is unavailable, and use applicationProtectedDataWillBecomeUnavailable: to set the value again when it is.
  • If the application is running in the background ( [[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground ) and no secure data is available ( [[UIApplication sharedApplication] isProtectedDataAvailable] == NO) ), just call exit(0) (or something similar) to exit the application. On the one hand, this seems like the simplest solution, and I see no flaws. But it also seems ... "wrong"? I guess I can’t decide if this is a pure solution or just lazy.
  • Anything else I just don't think about?
+7
source share
2 answers

After thinking about this for a while, I came up with a solution that I am pleased with. One thing to consider with the exit(0) option is that if the user needs some time to unlock the device, the application can constantly load, exit and restart. If you simply do not allow the application to do much, you will probably have to download it only once and will most likely be more efficient. So I decided to try my option 3 and see how randomly it was. It turned out to be easier than I thought.

First, I added the BOOL setupComplete property BOOL setupComplete for my application delegate. This gives me an easy way to check if the application was fully launched at various points. Then in application:didFinishLaunchingWithOptions: I try to initialize the managed object context, then I do something like this:

 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: is just a custom method that completes the setup. I'm not sure that beginIgnoringInteractionEvents needed, but I added that it is safe. Thus, when the application is brought to the forefront, I can be sure that the interface is frozen until the installation is complete. This can lead to a crash if an impatient user taps anxiously.

Then in applicationProtectedDataDidBecomeAvailable: I call something like this:

 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."]; } 

This completes the setup and turns on the interface again. This is most of the work, but you will also need to check your other code to make sure that everything that relies on Core Data is called before your permanent store is available. It should be noted that applicationWillEnterForeground and applicationDidBecomeActive can be called before applicationProtectedDataDidBecomeAvailable if the user starts the application from this background state. Therefore, in different places I added if (self.setupComplete) { … } to make sure that nothing starts until it is ready. I also had a few places where I needed to update the interface after loading the database.

To (partially) verify this without much driving, I temporarily changed application:didFinishLaunchingWithOptions: so as not to configure the database:

 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."]; } 

Then I moved the applicationProtectedDataDidBecomeAvailable: code to applicationWillEnterForeground: Thus, I can start the application, make sure that nothing unexpected happened, click the "home" button, open the application again and make sure that everything works. Since the actual code requires moving a considerable distance and waiting five minutes each time, this gave me a good way to get closer to what was happening.

The last thing that spurred me on was my regular store coordinator. A typical implementation might look something like this:

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

This is free based on the Apple code sample, which explains in the comments that you need to handle the error accordingly. My own code does a bit more than this, but one thing I haven't considered is that if the persistent storage load error, this will return a non-null result! This allowed all my other codes to act as if they were working correctly. And even if persistentStoreCoordinator was called again, it will simply return the same coordinator without having a valid storage, instead of trying to load the storage again. There are several ways you could handle this, but for me it would be better not to install _persistentStoreCoordinator, unless he could add storage:

 - (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; } 
+4
source

I experienced what you need to check

 [[UIApplication sharedApplication] isProtectedDataAvailable] 

and process

 applicationProtectedDataWillBecomeUnavailable 

to make sure that you do not have access to the protected file. Check for

 managedObjectContext 

didn't work for me.

0
source

All Articles