This is the first time I'm trying to make this work. I'm following the iCloud Programming Guide for Core Data, the "Using the SQLite Store with iCloud" section, and in AppDelegate I have:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MyApp.sqlite"];
NSError *error = nil;
NSString *failureReason = #"There was an error creating or loading the application's saved data.";
// To enable iCloud
NSDictionary *storeOptions = #{NSPersistentStoreUbiquitousContentNameKey: #"MyAppCloudStore"};
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:storeOptions error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = #"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
Previously, I enabled iCloud services for this App ID, create the corresponding provisioning, and turn on the iCloud services in Xcode's capabilities (iCloud Documents and default container).
According to the iCloud Programming Guide for Core Data, at this point:
Test: Run the app on a device. If Core Data successfully created and configured an iCloud-enabled persistent store, the framework logs a message containing “Using local storage: 1,” and, later, another message containing “Using local storage: 0”.
I run the app on an iPhone, and persistentStoreCoordinator is retrieved, but I see nothing in Xcode's debug area.
What am I missing?
Thanks?
I found the problem: [[NSFileManager defaultManager] ubiquityIdentityToken] was returning nil, and it was because I had my device not updated to iCloud Drive.
I found the response in this post.
Related
I need to move my app's database to a new location and stop it from being shared through iCloud automatically.
From my understanding, Apple previously documented that a database should be placed in the Documents directory of an application, but now says that the database should be placed in the Library directory. This is because iOS now shares all data from the Documents directory through iCloud.
My issue is that I need to move my database from the Documents directory to the Library directory. Below is the code I use to do this.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// Get the URL of the persistent store
NSURL *oldURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"App.sqlite"];
bool oldStoreExists = [[NSFileManager defaultManager] fileExistsAtPath:[oldURL path]];
// Get the URL of the new App Group location
NSURL *newURL = [[self applicationLibraryDirectory] URLByAppendingPathComponent:#"App.sqlite"];
bool shouldMoveStore = false;
NSURL *storeURL = nil;
if (!oldStoreExists) {
//There is no store in the old location
storeURL = newURL;
} else {
storeURL = oldURL;
shouldMoveStore = true;
}
NSError *error = nil;
NSDictionary *options = #{ NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES };
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Error Moving Store: %ld\nDescription: %#\nReason: %#\nSuggested Fix:%#", (long)error.code, error.localizedDescription, error.localizedFailureReason, error.localizedRecoverySuggestion);
abort();
}
if (shouldMoveStore)
[self movePersistentStoreFrom:oldURL To:newURL];
return _persistentStoreCoordinator;
}
-(void) movePersistentStoreFrom:(NSURL *)oldURL To:(NSURL *)newURL {
// Get the reference to the current persistent store
NSPersistentStore *oldStore = [_persistentStoreCoordinator persistentStoreForURL:oldURL];
// Migrate the persistent store
NSError *error = nil;
[_persistentStoreCoordinator migratePersistentStore:oldStore toURL:newURL options:nil withType:NSSQLiteStoreType error:&error];
if (error) {
NSLog(#"Error Moving Store: %ld\nDescription: %#\nReason: %#\nSuggested Fix:%#", (long)error.code, error.localizedDescription, error.localizedFailureReason, error.localizedRecoverySuggestion);
abort();
}
}
This code seems to work in moving the persistent store, but on the next run of the application the old store is found again and the app attempts to migrate the store a second time. At this point a crash occurs, and I'm assuming it is due to this double migration call. In classic Xcode style, the stack trace is pretty much useless.
Update -
The migratePersistentStore function does not remove ("cut/paste") the old sqlite database, instead it just makes a "copy". It looks like I'll need to remove the old one programmatically.
I switched the code to check if the newStoreExists as opposed to the !oldStoreExists, and the migration does work.
My new issue is that the table rows of the migrated data have become randomized. Any help with this would be appreciated!
Update 2 -
As it turns out, any data migrations like moving the store location or updating the database will randomize your data if you are using Core Data. You need to put an attribute into your table and use a NSSortDescriptor inside of your NSFetchRequest to manually sort the data and keep it ordered.
If you never migrate the data (I previously had not) the core data fetch request always come in the order it was saved.
I have an iOS 7 only app which uses Core Data for storage and I am bringing iCloud, with a switch to the app.
Every aspect of the iCloud integration is working, except the migration from the iCloud Store to the Local store if the user turns off iCloud from within the app.
Through the use of an Exception Breakpoint, the app is crashing with:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM replaceObjectAtIndex:withObject:]: object cannot be nil'
This is crashing at the "migratePersistentStore" code.
This is the code that performs that:
- (void)migrateiCloudStoreToTheLocalStoreAfterUserTurnedOffiCloudInSettings
{
// So we can see what's going on, we'll write out the current store URL before the migration
NSURL *storeURL = [self.persistentStoreCoordinator.persistentStores.lastObject URL];
NSLog(#"Current Store URL (before iCloud to Local migration): %#", [storeURL description]);
// NSPersistentStore *currentStore = self.persistentStoreCoordinator.persistentStores.lastObject;
NSPersistentStore *currentStore = [[self.persistentStoreCoordinator persistentStores] firstObject];
// We'll create a new URL
NSURL *localURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Envylope.sqlite"];
NSDictionary *localStoreOptions = nil;
localStoreOptions = #{ NSPersistentStoreRemoveUbiquitousMetadataOption : #YES,
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES};
NSError *error = nil;
[self.persistentStoreCoordinator migratePersistentStore:currentStore
toURL:localURL
options:localStoreOptions
withType:NSSQLiteStoreType error:&error];
NSLog(#"Current Store URL (after iCloud to Local migration): %#", [localURL description]);
// We'll write out a NSError here to see if there were any errors during the migration
NSLog(#"Error from iCloud to local migration %#", error);
// We'll just do a quick check to make sure are no errors with this procedure.
NSLog(#"Erros's after all of that %#", error);
NSLog(#"Current Store URL (after everything): %#", [localURL description]);
[self removeCloudObservers];
}
Issue
The app will crash, with the error above, at the migratePersistentStore line above. I cannot figure out what to do to get this working.
The commented out code for the currentStore shows that I've also tried checking for the lastObject, instead of the firstObject, and in both cases, I'm getting the same result.
I don't get any crashes from the local to iCloud because I want to make sure, if the user is using iCloud but then chooses not to, their data should be migrated locally.
This (Migrate Persistant Store Crash) Stack Overflow question seems like it would be a perfect fit, but firstly the answer isn't accepted and there's no confirmation that the code works from the person asking the question and also, that code didn't work for me; it simply removed the data.
Any guidance on this would really be appreciated.
With paying close attention to the link : Migrate Persistant Store Crash, I was able to get this working with the following code:
NSPersistentStoreCoordinator * persistentStoreCoordinator = self.persistentStoreCoordinator;
NSPersistentStore * persistentStore = [[persistentStoreCoordinator persistentStores] firstObject];
if([[NSFileManager defaultManager]fileExistsAtPath:localURL.path])
{
NSLog(#"File exists");
[[NSFileManager defaultManager] removeItemAtPath:localURL.path error:&error];
NSLog(#"Removing error = %#", error);
}
[[NSFileManager defaultManager] copyItemAtPath:persistentStore.URL.path toPath:localURL.path error:&error];
NSLog(#"The copying error = %#", error);
NSPersistentStoreCoordinator * newPersistentStoreCoordinator;
newPersistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
[newPersistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:localURL
options:localStoreOptions
error:&error];
NSLog(#"The adding error = %#", error);
Hopefully this will help someone with the same issue
I have a Sqlite file with preloaded data. I am using Core Data to read and display data from the Sqlite file.
Here is how I am reading the data:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL;
NSString *path = [[NSBundle mainBundle] pathForResource:#"myAppname" ofType:#"sqlite"];
storeURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// This dictionary automatically updates and migrates the store if it changes
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
[NSNumber numberWithBool: NO], NSReadOnlyPersistentStoreOption,
nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
if (error) {
[myErrorAlert showError:error];
}
CLS_LOG(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
However I need to update the Sqlite file with new data which I did outside the app with sqlite data editor.
Now when I copy-paste and override "myAppname.sqlite" file, I get the following error:
CoreData: error: (14) I/O error for database at /Users/Username/Library/Application Support/iPhone Simulator/7.1/Applications/45C373D7-8A54-48EF-BA3C-2E2C6AB7467F/myAppname.app/myAppname.sqlite. SQLite error code:14, 'unable to open database file'
I did try to add NSDictionary *options = #{ NSSQLitePragmasOption : #{#"journal_mode" : #"DELETE"} but still the same error exists.
Also some answers suggested to remove reference to file and create a new one, but I have no idea how to do it.
Any idea?
EDIT
I have manage to get it working.
Open original app on iOS 6 simulator.
Repalce Sqlite file with new file and run App only iOS 6 simulator than run on iOS7 simulator. NO issue.
However still would like to know other comments.
Is it possible to configure the level of protection of the SQLite file generated for the Core Data?
I need to use the NSFileProtectionComplete level on it.
Any ideas?
Look for the line where you do addPersistentStoreWithType:configuration:URL:options:
NSURL *storeURL = ...;
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:...];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error])
{
NSLog(#"Add persistent store failed: %#", error);
}
Then add:
NSDictionary *attributes = #{NSFileProtectionKey: NSFileProtectionComplete};
if (![[NSFileManager defaultManager] setAttributes:attributes
ofItemAtPath:path
error:&error]) {
NSLog(#"File protection failed: %#", error);
}
Be aware that you can't use the database in the background, consider using NSFileProtectionCompleteUnlessOpen:
NSFileProtectionComplete:
The file is stored in an encrypted format on disk and cannot be read from or written to while the device is locked or booting.
NSFileProtectionCompleteUnlessOpen:
The file is stored in an encrypted format on disk. Files can be created while the device is locked, but once closed, cannot be opened again until the device is unlocked. If the file is opened when unlocked, you may continue to access the file normally, even if the user locks the device. There is a small performance penalty when the file is created and opened, though not when being written to or read from. This can be mitigated by changing the file protection to NSFileProtectionComplete when the device is unlocked.
To pre-populate CoreData in my app upon first launch, I have included a PreModel.sqlite file that was previously created by the app from data that it downloaded from a web service, which includes images.
To populate the model, I do this:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSLog(#"creating new store");
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"PreModel.sqlite"];
if(![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
NSString *sqlitePath = [[NSBundle mainBundle] pathForResource:#"PreModel" ofType:#"sqlite"];
if (sqlitePath) {
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtPath:sqlitePath toPath:[storeURL path] error:&error];
if (error) {
NSLog(#"Error copying sqlite database: %#", [error localizedDescription]);
}
}
}
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]);
abort();
}
return _persistentStoreCoordinator;
}
It seems to work. But I have 2 questions:
If the sqlite file is just a database file and does not actually contain any images, how is the app finding and loading the images on first launch?
Even on subsequent runs of the app I see "creating new store" logged every time. Why is _persistentStoreCoordinator always nil? I am clearly setting it in the code.
It's possible to store images in a database file, usually as binary blobs (which look like instances of NSData in Core Data). If you can provide more info about your model or the code that stores/loads the images, we can be more specific.
"Creating new store" is expected to get logged every time the app is launched in this instance. Even though the SQLite file is persistent on disk, you can't expect data structures in your code to stick around when your app is terminated - you need to create a new persistent store object for your program every time it launches.
Think of it like assigning NSInteger x = 10, then expecting to be able to quit and relaunch your program while maintaining that x has the value 10. That's not how programs work - you'd need to reassign x = 10 before you can expect to read x and get 10 back. The variable _persistentStoreCoordinator works the same way here.