Monday, May 28, 2012

Core Data, Document-Based Application and Sandboxing

Every now and then one of my customers using Core Data Editor sends me a Core Data related question via e-mail. The last Core Data related question I got went like this:

I have a Core Data Document-Based application that is using the SQLite store. When I activate sandboxing I am not able to save the context anymore. Why is that and how can I work around this problem?
This question really caught my interest. Here is what I found out:

The save fails because the underlying SQLite library created temporary files on behalf of Core Data. You may have seen a file like this. Usually this file has the suffix "-journal". When you activate sandboxing your application is only are allowed to read and write to the store file itself and not to the "-journal" file. This explains why the save of the context fails. Until Apple has fixed this you can work around this by deactivating the creation of journal files. You can do this by sending the following pragma statement to the SQLite database:

pragma journal_mode OFF
or
pragma journal_mode MEMORY


Let me explain:
The first pragma statement completely disables the journaling of SQLite. You usually don't want to do this because you can't do a rollback anymore. The second pragma statement has less dramatic side effects: You can still rollback but the bookkeeping is done in memory. If your transactions are small this won't be a problem but you should be aware of the side effects.
Now that we know that its time to think about Core Data again. Core Data does not expose the SQLite database handle to the developer - with good reasons. Nevertheless there is a way that allows us to send the pragma statement to the SQLite database used by the SQLite store. When you add a persistent store to a coordinator you can specify an options dictionary. There is a key called NSSQLitePragmasOption that lets you specify another dictionary that contains the pragma statements you want to be executed by the SQLite store. I have described the procedure on Stackoverflow.com. Here is the code snippet that sets the journal mode to MEMORY:


NSDictionary *pragmaOptions = [NSDictionary dictionaryWithObject:@"MEMORY" 
                                                          forKey:@"journal_mode"];


NSDictionary *storeOptions = [NSDictionary dictionaryWithObject:pragmaOptions 
                                                         forKey:NSSQLitePragmasOption];


NSPersistentStore *store;
NSError *error = nil;
store = [psc addPersistentStoreWithType:NSSQLiteStoreType
                          configuration: nil
                                    URL:url
                                options:storeOptions
                                  error:&error];   

The last step is to bring it all together. In your NSPersistentDocument subclass you have to override the method that sets up the coordinator like this:

- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url ofType:(NSString *)fileType modelConfiguration:(NSString *)configuration storeOptions:(NSDictionary *)storeOptions error:(NSError **)error {
   
   NSDictionary *pragmaOptions = [NSDictionary dictionaryWithObject:@"MEMORY" 
                                                             forKey:@"journal_mode"];
   
   NSDictionary *options = [NSDictionary dictionaryWithObject:pragmaOptions 
                                                        forKey:NSSQLitePragmasOption];
   
   return [super configurePersistentStoreCoordinatorForURL:url ofType:fileType modelConfiguration:configuration storeOptions:options error:error];
}


A more "secure" solution is to create a package and put in your store file there. This requires no pragma-voodoo. You can find a discussion about this topic in the developer forums. (Account required)

If you have found a better solution please send me an e-mail.