UIManagedDocument does not save to disk - ios

I followed the Stanford iOS 7 course of Fall 2013 and I'm getting used to all the concepts, though I encounter a problem with Core Data's UIManagedDocument and persistent saving.
My application is similar to the courses one, and all the Model/Object works fine and displays correctly. The code does not differ much from the provided material; all with instantiation, file handling, object context and that stuff and with some NSLog debugging and manual save control I made sure, the context saves all the changes made to it (e.g. via the notification the UIManagedDocument fires).
What does NOT work is keeping that stuff on file. I mean, it does not save it to file, though it says it saves the context. I thought that was related to autosaving, so I created a Button in the UI to manually do that. But still no persistent save. I have no idea how that can be. I checked the storage first with the App with the parts that actually add objects to the document being disabled. Then I moved on to use a third party app that can display the contents of an apps storage. The structure is there, but no saved data.
I came across a website, where someone said that can be related to missing any required values in the model when setting the objects values. Not the case, tested with a subclass of UIManagedDocument with that handleError: thing.
Anyone else got this? Data structure is fairly simple, just one entitity with about 6 strings and one other type of value. I don't want to post all the code here, since it's a lot. If you have a suspicion on what it can be I can post parts of the code.
Code of creating the document in AppDelegate.m:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *url = [documentsDirectory URLByAppendingPathComponent:#"AppDocument"];
DebuggingManagedDocument *document = [[DebuggingManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
document.persistentStoreOptions = options;
if ( [fileManager fileExistsAtPath:[url path]] ) {
[document openWithCompletionHandler:^(BOOL success){
if ( !success ) {
NSLog(#"Could not open document.. :(");
} else {
[self documentIsReady:document];
}
}];
} else {
// Create
[document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if ( !success ) {
NSLog(#"Could not save document... :(");
} else {
NSLog(#"Saved document!!");
[self documentIsReady:document];
}
}];
}
Code of
Regards

Try using the JOURNAL=DELETE mode persistentStoreCoordinator option instead of the default WAL mode.

Related

How to open CoreData database file to see saved values

Right now i am using coredata to save my data.
Its all working fine but now i changed the logic to save values to the database. So i need to compare that the after logic change same values are getting saved in tables. So I need to compare tables.
I went through many links like
link 1
link 2
all the links shows that the database file that coredata create is of .sqlite extension. But the files create at that location are "persistentStore, persistentStore-shm, persistentStore-wal" as show in screenshot.
How should i suppose to open these files to see the saved data in tables.
Thanks in advance
- (void)setupDatabase:(void (^)(BOOL))completionHandler
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"MainDataModel"];
self.db = [[CWUIManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
self.db.persistentStoreOptions = options;
if(![[NSFileManager defaultManager] fileExistsAtPath:[self.db.fileURL path]])
{
[self.db saveToURL:self.db.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
self.dataManager.db = self.db;
completionHandler(success);
}];
} else if (self.db.documentState == UIDocumentStateClosed) {
[self.db openWithCompletionHandler:^(BOOL success) {
self.dataManager.db = self.db;
completionHandler(success);
}];
}
}
Your sql file is the one without extension. The other 2 files are for journaling mode.
This has already been explained in this question. Apple changed the default journaling mode to WAL on iOS 7.
In setupDatabase() method replace the following line
url = [url URLByAppendingPathComponent:#"MainDataModel"]
with
url = [url URLByAppendingPathComponent:#"MainDataModel.sqlite"]
You have to create the persistent store type as Sqlite
Run these commands on terminal and open yourpersistentStore in sqlite manager. These commands will merge the WAL file into the main sqlite file
$ sqlite3 persistentStore
sqlite> PRAGMA wal_checkpoint;
Press control + d
These steps are already covered in the answer of the above link : https://stackoverflow.com/a/43406516/468724

App Crashes at startup on App Store review - works perfectly on devices

I have an app that crashes on startup in the app store review, but works perfectly otherwise on devices and on the simulators. Here are the specifics.
The app is written for IOS 7, and is being tested on IOS 8.x. I symbolicated the crash logs from Apple, and it is crashing on the first attempt to access information stored in the pre-populated Core Data sqlite db.
This code does the db copy at startup:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CC.sqlite"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *storePath = [documentsDirectory stringByAppendingPathComponent: #"CC.sqlite"];
// Check if the sqlite store exists
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath]) {
NSLog(#"sqlite db not found... copy into place");
// copy the sqlite files to the store location.
NSString *bundleStore = [[NSBundle mainBundle] pathForResource:#"CC" ofType:#"sqlite"];
[[NSFileManager defaultManager] copyItemAtPath:bundleStore toPath:storePath error:nil];
}
else {
NSLog(#"Already exists");
}
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = #{ NSSQLitePragmasOption : #{#"journal_mode" : #"DELETE"} };
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"error %#, %#", error, [error userInfo]);
}
return __persistentStoreCoordinator;
}
We have run this a couple of hundred times on various IOS devices (by several different people) without any problems at all, but invariably get a startup crash at Apple.
It is clear to me from the crash logs that the app (during app store review) is trying to access a non-existant sqlite db through Core Data, but I have no idea why this is happening only at Apple, and why I cannot reproduce the error. I am not sure what other info to add to the question but will happily update as required.
Any advice gratefully received....
I will answer my own question here, although the code changes I made while trying to solve the problem makes posting the code not very helpful.
I had to put the startup database operations in a completion block. This involved moving everything into my initial startup view controller, and disabling my tab bar buttons while the database was initialized.
What was happening was certain items in the Core Data sqlite database were being required before it was completely preloaded, thus the crash.
More on blocks can be found here

What is the standard(or correct) way to use UIManagedDocument in core data

I am learning iOS via Standford CS193P, I met a problem in core data part.
I am trying to build an app using core data, I create a category for the AppDelegate(I will create a UIManagedDocument in didFinishLaunchingWithOptions), this category implement one method to create a UIManagedDocument and return it to the AppDelegate, so that I can call self.managedDocument = [self createUIManagedDocument] to get one:
- (UIManagedDocument *)createUIManagedDocument
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSURL *url = [documentsDirectory URLByAppendingPathComponent:APP_NAME];
UIManagedDocument *managedDocument = [[UIManagedDocument alloc] initWithFileURL:url];
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
// If the file exists, open it
NSLog(#"File exists, not opening...");
[managedDocument openWithCompletionHandler:^(BOOL success) {
NSLog(#"File opened.");
}];
} else {
// If the file not exists, create it
NSLog(#"File not exists, now creating...");
[managedDocument saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
// If the file create succesfully, open it
[managedDocument openWithCompletionHandler:^(BOOL success) {
NSLog(#"File opened.");
}];
}];
}
return managedDocument;
}
After the UIManagedDocument creation, I am trying to pass this UIManagedDocument to my view controllers using:
RootViewController *rootViewController = (RootViewController *)self.window.rootViewController;
rootViewController.managedDocument = self.managedDocument;
And the problem occurs, I am unable to open the UIManagedDocument in my view controllers. I searched it for a whole day and got the answer: I was trying to open it twice at the same time while it is asynchronous, it takes time to process the IO request. There are some approaches, and most of them using Singleton.
Here is my question:
Can I do this by using the approach above?
If no, what is the standard(or correct) way to create and pass this UIManagedDocument around my app?
Should I pass NSManagedObjectContext in this UIManagedDocument to my view controllers instead of passing UIManagedDocument?
Thank you for reviewing my question.
In the course, we are told to do this using notifications, he explains this in a demo in lecture 13 at 51:20.

Core Data: handle the case when iCloud turns off / on

I am implementing iCloud support in my Core Data app (iOS 7 only, not released yet, iCloud support will be from Day I). I've checked out WWDC 2013 207 session about changes in iCloud and I am really glad to see the improvements (I had some previous experience with iCloud too).
Things are working really great. However, I am not sure how to handle the case when user enables or disables iCloud from system preferences — this results to creating new .sqlite files in another directory and therefore user data loss.
Here's how I implement persistent store adding:
- (void) addPersistentStoreToCoordinator {
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:#YES forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:#YES forKey:NSInferMappingModelAutomaticallyOption];
NSURL *iCloud = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier: nil];
if (iCloud) {
[options setObject:#"ABC123~com~myapp~myapp" forKey:NSPersistentStoreUbiquitousContentNameKey];
}
NSError* error;
// the only difference in this call that makes the store an iCloud enabled store
// is the NSPersistentStoreUbiquitousContentNameKey in options.
[persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.storeURL
options:options
error:&error];
}
- (NSURL *)storeURL {
NSURL *documentsDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:NULL];
return [documentsDirectory URLByAppendingPathComponent:#"MyApp.sqlite"];
}
How do I save data from Core Data when user switches off/on iCloud preference in the settings?
(Side note: I've managed to manually handle the case when iCloud goes from state [OFF] > [ON] — fetching all data from old Core Data, then saving these NSManagedObject subclasses to newly created Core Data with iCloud enabled. However, this code is very app-specific (and pretty unstable). I am looking for more generic solutions).
Thanks
I had the exact same questions and found this https://gist.github.com/mluisbrown/7015953 . Works perfectly for me now.

Core Data - lightweight migrations and multiple core data model files (xcdatamodel)

I'm having a problem performing a lightweight migration when migrating from a store that is defined by two separate xcdatamodel files.
In version 1.0 of my app, I had the models broken out into an analytics model, model-A, and everything else in model-B. When compiling, the models would be grouped together and everything proceeded smoothly.
When working on the new version, 1.1, I upgraded model-B by adding a new model version to model-B and setting that new version as active.
The issue arises when upgrading from 1.0 to 1.1. It seems Core Data checks the model store on disk (created by version 1.0) and looks for the model that describes it but is unable to find a SINGLE model that defines the entire store (model-A only covers analytics, and model-B covers everything else), so it throws a "Can’t find model for source store" error.
Has anyone found a solution for separating out models but still allowing upgrades + lightweight migrations to work without the extra hassle of defining custom migrations?
Here is the snippet of code used to load models:
NSArray *modelNames = [NSArray arrayWithObjects:#"model-A", #"model-B", nil];
NSMutableArray *models = [NSMutableArray array];
for (NSString *name in modelNames)
{
LogInfo(#"loading model %#", name);
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:name withExtension:#"momd"];
NSManagedObjectModel *model = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] autorelease];
[models addObject:model];
}
// combine all the separate models into one big one
objectModel = [[NSManagedObjectModel modelByMergingModels:models] retain];
NSURL *documentsDirectory = [NSURL fileURLWithPath:[SuperFileManager documentsDirectory] isDirectory:YES];
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"database.sqlite"];
NSError *error = nil;
coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objectModel];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
After attending a WWDC 2012 lab and meeting with the Core Data team, it seems you are forced to put all your model info in a single xcdatamodel. CoreData is not intelligent enough to check its existing stores as a combination of the stores that created it and are still on disk. As C. Roald pointed out, you can do some processing on old xcdatamodel files, but it's quite sad that Core Data does not handle this more elegantly.
I encountered this problem also. I lost several hours trying to figure out WTF -- very frustrating.
I believe the easiest way to solve this problem is:
Pick which model you're keeping -- say ModelB -- and create a new version for it based on the published version. I'll call the published version ModelBv1 and the new version ModelBv1_merge.
Open contents XML files for ModelAv1 and ModelBv1_merge in a text editor (ie, ModelA.xcdatamodeld/ModelAv1.xcdatamodel/contents and ModelB.xcdatamodeld/ModelBv1_merge.xcdatamodel/contents) and merge the XML by hand. The schema is very simple -- just copy the <entity> elements and merge the <elements> element (into the _merge contents file) and you're done.
Open the contents file for your new ModelBv2 and again merge ModelA contents into it.
Remove ModelA from your project file.
Check in Xcode that ModelBv1_merge and ModelBv2 look sane, and contain everything you expect (the union of old Model A and Model B). Build and you should be done.
(I think this has a caveat of "provided both contents files were written by the same version of Xcode", but I think if you have an old contents file it should be easy enough to make Xcode rewrite it by making a trivial change somewhere.)
I have a scenario in which my application model is obtained merging multiple models, and I managed to have a kind of automatic lightweight migration in this way:
NSError* error = nil;
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"db.sqlite"];
NSString* storePath = [storeURL path];
NSLog(#"Store URL: %#", storeURL);
if( [[NSFileManager defaultManager] fileExistsAtPath:storePath] ){
// Load store metadata (this will contain information about the versions of the models this store was created with)
NSDictionary *storeMeta = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:nil URL:storeURL error:&error];
if(storeMeta){
// Get the current model, merging all the models in the main bundle (in their current version)
NSManagedObjectModel* model=[NSManagedObjectModel mergedModelFromBundles:nil];
// If the persistent store is not compatible with such a model (i.e. it was created with a model obtained merging old versions of "submodels"), migrate
if(![model isConfiguration:nil compatibleWithStoreMetadata:storeMeta]){
// Load the old model
NSManagedObjectModel*oldModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:storeMeta];
// Compute the mapping between old model and new model
NSMappingModel* mapping = [NSMappingModel inferredMappingModelForSourceModel:oldModel destinationModel:model error:&error];
if(mapping){
// Backup old store
NSURL* storeBackupURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:#"db.sqlite.%#.bck", [NSDate new]]];
BOOL done = [[NSFileManager defaultManager] moveItemAtURL:storeURL toURL:storeBackupURL error:&error];
if(done){
// Apply the mapping
NSMigrationManager* migrationManager = [[NSMigrationManager alloc] initWithSourceModel:oldModel destinationModel:model];
BOOL done = [migrationManager migrateStoreFromURL: storeBackupURL
type: NSSQLiteStoreType
options: nil
withMappingModel: mapping
toDestinationURL: storeURL
destinationType: NSSQLiteStoreType
destinationOptions: nil
error: &error];
if(done){
NSLog(#"Store migration successful!!!");
}
}
}
}
}
}
if(error){
NSLog(#"Migration error: %#", error);
}
The best way to upgrade your Core Data model is to add a version. If you don't do that, you will perish in crashes, update perils and stuff like that.
Adding a new version is actually quite easy. You select the datamodel file and select 'Editor > Add model version'. This will enable you to add a new database version based on the previous model. You then need to set the current datamodel to the latest: http://cl.ly/2h1g301b0N143t0b1k2K
iOs will migrate the data automatically (at least in my case) when a new version is installed.
hope this helps.

Resources