What is the correct way to delete a UIManagedDocument? - core-data

I can't seem to find any reliable documentation that explains the correct procedure for deleting a UIManagedDocument, and in particular one where the iCloud options have been turned ON.
I understand that this option would delete the file at this fileURL. And this would appear to be fine if iCloud is not being used.
[[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error];
If iCloud is being used then CoreData creates files all over the place, including in /Document/CoreDataUbiquitySupport and in the iCloud /CoreData folder. So in this case is it up to me to call removeUbiquitousContentAndPersistentStoreAtURL for each store in the UIManagedDocument prior to calling [NSFileManager removeItemAtURL]. If so is this documented somewhere ?
[NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:storeURL
options:#{NSPersistentStoreUbiquitousContentNameKey:fileName,
NSMigratePersistentStoresAutomaticallyOption:#YES,
NSInferMappingModelAutomaticallyOption:#YES,
NSSQLitePragmasOption:#{ #"journal_mode" : #"DELETE" }}
error:&error];

Here is my two cents on the issue. I tried what dtrotzjr recommended and didn't have a whole lot of success. It seems that removeUbiquitousContentAndPersistentStoreAtURL:options:error: is great for clearing out data in a UIManagedDocument, but the Logs folder is still there and so are the remnants of the file I'm trying to delete. Here is a simpler method for completely deleting a UIManagedDocument from iCloud or Local Docs:
+ (void)deleteDocumentURL:(NSURL *)url{
//if we have an iCloud Document, remove it from the UbiquitouseKeyValueStore
if ([self isiCloudURL:url]) {
[[NSUbiquitousKeyValueStore defaultStore] removeObjectForKey:[url lastPathComponent]];
}
//do the delete on another thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSError *coordinationError;
[coordinator coordinateWritingItemAtURL:url
options:NSFileCoordinatorWritingForDeleting
error:&coordinationError
byAccessor:^(NSURL *newURL) {
NSError *removeError;
//code for performing the delete
[[NSFileManager defaultManager] removeItemAtURL:newURL error:&removeError];
//if we have an iCloud file...
if ([self isiCloudURL:url]) {
//remove log files in CoreData directory in the cloud
NSURL *changeLogsURL = [[self urlForiCloudLogFiles] URLByAppendingPathComponent:[url lastPathComponent]];
[[NSFileManager defaultManager] removeItemAtURL:changeLogsURL error:&removeError];
}
}];
});
}
This is pretty much the code from Stanford's CS193 course 2012 + the delete for the changeLogs folder and it works on local and iCloud docs. Please let me know if you see any issues with performing the delete this way.

For iCloud core-data content you want to call the static method removeUbiquitousContentAndPersistentStoreAtURL:options:error: on the NSPersistentStoreCoordinator class, then call removeItemAtURL:error:
See deleteManagedDocumentWithIdentifier: in my APManagedDocument project. This is on the ubiquitous_experiment branch which I am currently working on finalizing before I merge it back down to the master branch.

Related

RestKit/CoreData not saving relationships immediately to the persistent store

Does RestKit or CoreData has some weird mechanism of context save? I mean I download my Managed Objects and if I kill the app quickly and run it again I see that some relationship objects are not saved to persistent store. However when I wait like 10-15 seconds before killing the app these object get saved and I can fetch them when running the app again.
So how does it work? Is it normal that the objects are not saved in transaction-like operation (either the whole object with its relationships or nothing)?
Maybe I was just lucky with these 15 seconds and it is possible that these relationship objects wont be saved at all in some circumstances due to some bug in CoreData/RestKit/my code?
I download objects using:
RKObjectManager *manager = [RKObjectManager sharedManager];
[manager getObjectsAtPath:#"/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"OK");
[self saveContext];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"ERROR");
}];
And save context by:
[[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext save:&err];
Any help?
RestKit saves the context for you before calling the success block - you do not need to explicitly save.
By "kill the app" I guess you are stopping it in Xcode? This is a full termination and anything that isn't quite finished yet will not get a chance to. This is unrealistic testing and you shouldn't base much on it.
If you want to know exactly when a save operation has completed, observe the appropriate notifications that are posted.

New project New Model NSPersistentDocument This NSPersistentStoreCoordinator has no persistent stores

I have been searching stackoverflow and Googling for hours. I made a simple project to mess around with Core Data and bindings. It added an entity to the model and it wouldn't work any more. I was getting "This NSPersistentStoreCoordinator has no persistent stores It cannot perform a save operation" whenever I tried to add data to a new document. I followed every piece of advice I could find with no luck.
Finally, I made a new project (NSPersistentDocument based) and I made a new model from scratch. I made sure the model was perfect before I ran the project for the first time.
In WindowControllerDidLoadNib: The project calls a method to add data. Before addData routine, I log the ManagedObjectContext and the ManagedObjectModel. Neither of them are nil.
I am still getting this %$&##! error.
Does anyone have any new ideas about this?
EDIT: Could this be because the new untitled document has never been saved? If so, how do you get around that? Can you save an untitled document? Do you really want to?
I had a similar problem a while back on a file import. Since I had full control, I named and saved the document and then I was able to save the context.
As I indicated in the comment above, at least in Mountain Lion you have to save the document at least once before you can save the context. I did some experiments and the small amount of data that I changed was preserved by autosave without saving the context. I have changed my saveContext method to the following:
- (void)saveContext {
if (![self fileURL]) {
NSLog(#"Can't save context. No file name has been set.");
return;
}
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
[NSApp presentError:error];
NSLog(#"Error userInfo: %#",[error userInfo]);
abort();
}
}

Moving playlist to folder doesn't work

I'm having issues moving a newly created SPPlaylist to a (possibly newly created) SPPlaylistFolder.
The idea is to create a folder in the user's Spotify account, where I can add playlists generated from my application. If no such folder has been created, I'm creating a new SPPlaylistFolder and save the folder id for later use.
This is what I'm doing (I've omitted parts of the code that aren't interesting to this subject):
If a folderId has been previously saved (i.e. a folder created), use that ID to load the folder instance:
...
NSError *error = nil;
if (folderId > 0) {
// try to fetch folder
folder = [[SPSession sharedSession] playlistFolderForFolderId:folderId inContainer:container];
}
if (folder == nil) {
// create folder
folder = [container createFolderWithName:#"My Folder" error:&error];
// save a reference to the folder in an instance var
_appFolder = [folder retain];
// (also saving folder.folderId in NSUserDefaults)
}
...
Create an SPPlaylist: [[[SPSession sharedSession] userPlaylists] createPlaylistWithName:#"My Playlist"].
Use KVO to observe the container's playlists property and get notified when the playlist has been created: [[[SPSession sharedSession] userPlaylists] addObserver:self forKeyPath:#"playlists" options:0 context:nil].
Observe the playlists property and move the created playlist to my SPPlaylistFolder (containerPlaylist is the playlist I've identified as the one to move):
...
// identify the index of the containerPlaylist
NSInteger playlistIndex = [[[[SPSession sharedSession] userPlaylists] playlists] indexOfObject:containerPlaylist];
// move playlist
NSError * error = nil;
BOOL success = [container movePlaylistOrFolderAtIndex:playlistIndex ofParent:nil toIndex:0 ofNewParent:_appFolder error:&error];
if (success) {
// This should be a great success. But the playlist hasn't been moved, although the error variable is nil.
}
...
After these steps, both the playlist and folder have been created, but the playlist hasn't been moved. And I'm not getting any errors indicating any invalid input to the movePlaylistOrFolderAtIndex method.
Am I missing something obvious here? Or is the move functionality flawed somehow?
Note: I have also tried to use this code to move playlists that have been created previously (i.e. move all playlists named "My Playlist" to the folder).
EDIT 1: I've investigated this a bit further and actually got some moving action going on. But I had to rewrite some of the code and perform the move several times (or at a later stage). It seems like this is related to the data in SPSession not being entirely synced/up-to-date (?) since it's possible to move playlists when logging later with new session.
Is it possible that it's a syncing issue, i.e. libspotify believes that the SPPlaylistFolder is created and moves SPPlaylists to it, without it actually being created yet?
After having updated my code with reference to this issue on cocoalibspotify, it's working better. What I didn't realize at first was how the syncing with the Spotify service worked. It can easily take several minutes for the changes to be reflected in the Spotify desktop client, for example.

How do you handle a UIManagedDocument?

First off, I should mention that this is my first post on this site. I am trying to teach myself to program iOS and in my google searches for answers I find that I'm constantly directed here. So thank you to all who have contribute here. You have help me a ton already.
I have been going through the Stanford CS193P class and LOVE it. But I'm stuck right now and not sure where to turn.
My problem has been with the UIManagedDocument.
I tired to make a simple app to test my new skills. This is what it does:
A simple accounting app that tracks individual contributions to a fundraising event.
I have a UITabBar that on each tab allows you to:
1. Track the participants (Players) - This will connect to the address book and allow you to add them or just keep them in this app.
2. Manage the events (Events) - You can add, edit or delete events that you will then add participants to and then be able to add what they brought (Bank) in on that event.
3. Settings. - I've added some buttons just to help me figure stuff out now including a reset button that clears all data and a "dummy data" button.
I have three coreData Entities. Players, Events and Bank each with relationships with the other two.
When I first tried to make this app (pre iOS5) I used the appDelegate to create my ManagedObjectContext and pass it around to my viewControllers. That worked. But now I'm supposed to use the UIManagedObjectDocument and not use the AppDelegate. I believe I understand the principle and the integration to iCloud. (I could be wrong)
Using the examples from the class and what I could find online I made a helper class that will provide the first of each of my ViewControllers within my UINavigationControllers the ManagedDocument.
+ (UIManagedDocument *)sharedManagedDocument
{
static UIManagedDocument *sharedDocument = nil;
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"DefaultAppDatabase"];
// url is "<Documents Directory>/<DefaultAppDatabase>"
// Create the shared instance lazily upon the first request.
if (sharedDocument == nil) {
sharedDocument = [[UIManagedDocument alloc] initWithFileURL:url];
}
if (sharedDocument.fileURL != url) {
UIManagedDocument *newDocument = [[UIManagedDocument alloc] initWithFileURL:url];
sharedDocument = newDocument;
}
NSLog(#"SharedDocument: %#", sharedDocument);
return sharedDocument;
}
I then had that first ViewController open the document and perform the fetch.
From there I pass whatever NSManagedObject is selected to the next ViewController through the segue.
The problems are when I get to adding or reseting the data. (I'm assuming that if I can get it to work with the "dummy data" button I can get it to work on an individual entry) When I press the "Reset" or "Dummy" button my logs tell me that it was pushed but I don't see any change in the data until I restart the app. Then it shows up perfectly. My guess is I'm not saving the file correctly or I'm not refreshing the tableViews correctly. I made a small attempt at using NSNotification but didn't go to far into it since I couldn't get it to respond to anything. I'm happy to go back down that road if I need to.
This is my save method... pretty much just copied from the default coreData appDelegate.
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.appDatabase.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.appDatabase saveToURL:self.appDatabase.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success){
if(!success) NSLog(#"failed to save document %#", self.appDatabase.localizedName);
if(success) NSLog(#"Success: save document %#", self.appDatabase.localizedName);
}];
}
Paul, the instructor from CS193P, suggested in Assignment 6 to use a helper method to pass around the UIManagedDocument through a block. I get the theory behind blocks but haven't completely wrapped my head around them so I haven't ruled out that my answer may lie there as well.
Thank you so much for any help or pointing me in the right direction to do more research. Sorry this post is so long, I was trying to be as clear as I can.
I was over thinking the whole thing. Alan asked my question perfectly in this post:
How do I create a global UIManagedDocument instance per document-on-disk shared by my whole application using blocks?
The question and the answers cleared everything up.
Thanks

Dump after some changes in CoreData Model

I did some changes at my CoreData Model. So far, I added a attribute called 'language'. When my application is launched and I click on "Create new Customer" a instance variable Customer is created. This variable is created by:
Customer *newCustomer = [NSEntityDescription insertNewObjectForEntityForName:#"Customer" inManagedObjectContext:appDelegate.managedObjectContext];
Before I did these changes everything worked fine and as planned. But now i get a dump with this error message:reason = "The model used to open the store is incompatible with the one used to create the store";
What do I have to do to solve this? reseting the persistence store didn't help so far.
What I did to get around this problem was to add this
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
to my appDelegate in the persistentStoreCoordinator before adding the persistent store. This deletes the existing store that no longer is compatible with your data model. Remember to comment this line before you run the application the next time if you want to keep what is stored.
My implementation of the persistentStoreCoordinator looks like this when I have to remove an old store.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSError *error = nil;
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"MyPinballScore.sqlite"]];
//The following line removes your current store so that you can create a new one that is compatible with your new model
[[NSFileManager defaultManager] removeItemAtURL:storeURL 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_;
}
The answer is a bit tricky but this always works for me. This is for a clean installation of a new compatible .sqlite file, not a migration!
launch simulator, delete the app and the data (the popup after you delete the app).
quit simulator
open X-Code, after making any edits to your data model
delete the {*appname*}.sqlite file (or back it up, remove it from project folder, and delete reference)
clean the app (Product > Clean)
Run the app in a simulator (for this tutorial I will assume 4.2)
While the simulator is running, in a Finder window, navigate to:
{*home*} > Library > Application Support > iPhone Simulator > 4.2 > Applications > {*random identifier*} > Documents > {*appname*}.sqlite
Copy this file to another location
Stop running your app in X-Code
Drag and drop the {appname}.sqlite file into the files list in X-Code.
In the dialog that pops up, make sure the copy to folder checkbox, is checked.
Product > Clean
Then run the app in the simulator again
Now you should have a working sqlite file!
Cheers,
Robert

Resources