I'm starting in coreData, and I have a doubt.
I have an App that reads data from a server, I parse the data, and get as NSDictionary of Objects.
To save the data to coreData, i do the following:
for (NSDictionary *activityData in arrayWithResult){
[CompanyActivity createActivityWithInfoFromServer:activityData inManagedObjectContext:self.managedObjectContext];
}
if (![self.managedObjectContext save:&error])
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
This reads about 300 records. The method 'createActivityWithInfoFromServer:' checks if there is any record with that name. If so, updates the data, if not, creates one.
The problem is that, while the "for" cycle is running, if i interact with the user interface, it stops saving in core data, sometimes, not always. Why?
If I take the SAVE inside the cycle, the problem disappears.
What should I do?
Thanks all,
RL
Depending on what your reasoning is to still allow the user to interact with the UI, you could either disable the UI for that time, or create a new thread that handles the piece of code that you posted.
As for the reasoning why it only fails to save sometimes, I assume that sometimes it doesn't have to create as many new objects and finishes sooner, and can save before you interact with the UI. If you save inside the for loop than you are saving everything after looking at each item which is fairly inefficient.
Related
I once again come about my RSS Reader iApp which, at the moment, is designed the following way:
Data model: Category -> Feed -> Post
Master View: Feeds grouped by categories
Detail View: Posts for a given Feed
All of the views and the app delegate only interact with the same _mainMOC (ManagedObjectContext).
Each of the created NSOperations will use its own _localMOC which is connnected to the same NSPersistentStoreCoordinator.
When the App starts, it creates for each of the Feeds a fetchOp NSOperation which it adds to an NSOperationQueue. This should ensure the RSS Feeds will be individually downloaded, parsed, then their contents inserted Post by Post in CoreData.
The AppDelegate observes the NSManagedObjectContextDidSaveNotifications and merges the modifications if the notification it receives is not _mainMOC. It then sends a specific NSNotification to notify the each of the views that a reloadData is required.
Questions:
Do I still need to enclose my _mainMOC save: operations in performBlock blocks?
Should each of my views only use a _localMOC instead of the _mainMOC?
Isn't it redundant to have each view's local NSFetchedResultsController re-perform a fetch before a reloadData?
Should I make any non-read-only Core-Data Operation a queued NSOperation, even at the view level?
How could I make all of this smoother (it still isn't and I still have a few horrendous bugs so redesign is a possibility)...
Thanks for your help.
1) & 2) It doesn't appear these are absolutely required: we are in the main thread, after all.
3) This couldn't hurt: especially if the NSFecthedResultsController uses a variable NSPredicate (see here).
4) I am going to do it anyway as it doesn't hurt.
5) Keep in touch...
Let's say I have a NSManagedObject named «Picture» that I have created via RestKit.
I'm uploading the actual picture file content through a NSOperation. That NSOperation is tracking the upload progress, and saving it in the «progress» attribute of the Picture object.
if(![self isCancelled]) {
Picture *pic = [Picture findFirstByAttribute:#"pictureId" withValue:self.pictureId];
if(![pic isDeleted]) {
pic.progress = [NSNumber numberWithFloat:progress];
[[RKObjectManager sharedManager].objectStore save:NULL];
}
}
My view controller is displaying a list of Picture objects, with the help of a NSFetchedResultsController.
Upload works great, RestKit is doing the thread safety and the merge back to the main NSManagedObjectContext, so the UI is displaying the upload progress as expected.
Imagine now that while uploading the picture the user presses the «cancel» cross, which is also supposed to delete the Picture object.
In the controller, I'm calling a cancel on the NSOperation, which effectively stops the running the operation within milliseconds, and the object is deleted from CoreData on the main thread.
[uploadOperation cancel];
Picture *pic = [Picture findFirstByAttribute:#"pictureId" withValue:self.pictureId];
[pic deleteEntity];
[[RKObjectManager sharedManager].objectStore save:NULL];
Traces show that I get a NSFetchedResultsChangeDelete on my NSFetchedResultsControllerDelegate as expected. However it is immediately followed by a NSFetchedResultsChangeInsert and a NSFetchedResultsChangeUpdate, because the saving of the progress is made really frequently by the NSOperation.
It seems that the NSManagedObjectContext used by the NSOperation is out of sync. It doesn't know that the Picture object is deleted, the call to save: causes an insert and an update.
So in my UI (and in DB), my Picture object remains while it's supposed to be deleted.
Looking at RKManagedObjectStore, it seems that merges are made from background threads (NSOperation) to main thread (my view controller) but not the opposite way.
What's the best approach to fix this issue ?
I think you should probably change the control flow of the app a bit. Deleting the object in the main context while the NSOperation is still in progress sounds like a bad idea. Why don't you e.g. check if the operation was cancelled in the NSOperation itself and delete the image objects from there, using its MOC? In that way you only have writes on one child MOC. The alternative, i.e. updating the child MOC in the operation before every modification defeats the purpose of having separate contexts.
Thinking about it, there is probably some clever way of observing NSManagedObjectContextObjectsDidChangeNotification and merging with an appropriate NSMergePolicy, but then again, I think that's overkill for your case. I'd just handle the entire cancellation and deletion in the same place.
Sounds like the perfect situation to use the Core Data Undo Manager. You'll find a good example over here or in the CoreDataBooks Example provided by Apple.
I have two instances of the same ViewController class accessed in different tab items. Both use the same entity, but with a different predicate. One displays all the items, while the other displays a subset based on its predicate.
The problem occurs when I delete an object from the "All" list. It updates immediately, but when I switch over to the other tab, the object is still there, even after going back and forth in the views. Only after a period of time, around 5 to 10 seconds, does the deletion get reflected in the other view.
The ViewController class use a FetchedResultsController.
Any ideas what the cause is and how to get the results to immediate appear?
Just put a reloadData into viewWillAppear. You can also catch this when the tab bar's selected index changes.
Apparently, there is no solution. There is no way to update UIManagedDocument manually.
This guy came to the same conclusion:
Core Data managed object does not see related objects until restart Simulator
So the solution is to use the default master-detail template and to stop using UIManagedDocument. Wish there was some documentation on this, would have saved me a day of my life.
I can't seem to get a clear answer for this: when you change a transient property, and then call save, should the NSManagedObjectContextDidSaveNotification be triggered? In my notification listener, how can I filter out these notifications that are coming from changes in transient properties?
Here's what I'm trying to do: I want to load up a list of contacts in the main thread, and when it's done, I want to read the images in a background thread from the address book and attach them to the contacts. This works fine on the face of it: after loading from the Contacts entity, I use a dispatch queue to loop through all the contacts, find their image in the Address Book, and save them in Contact's "contactImage" property (which is transient). The dispatch queue then successfully reloads the tableview (on the main thread) and the images show up next to the contacts.
The problem is that if I do anything to the contact that invokes a "save" on even ONE of the managed objects (for e.g. I delete one of the contacts), the NSManagedObjectContextDidSaveNotification is invoked for ALL the contacts. I've found that this is because the contactImage property was changed before ... commenting that the "self.contactImage = img;" line makes the issue go away. This is surprising to me, since I would have thought that the save notification would only be called for non-transient properties.
Can anyone confirm if this is expected behavior? Or am I doing something wrong? If it's expected, how do you filter out the updates to transient properties in the NSManagedObjectContextDidSaveNotification listener? I need to do some post-processing in the listener, and I don' want to do it needlessly for transient property updates. I've checked the changedValues dictionary on the NSManagedObject, but it seems to show empty inside the listener (since only transient properties changed, I'm guessing).
Thanks.
Yesterday,
Transient properties have one key characteristic -- they are managed. You can easily add ivars that are not managed to any NSManagedObject. If you do so, they are not subject to -save: notifications.
A related question: Why are you using a transient ivar? They have some specialized uses; primarily, they are used to trigger property updates throughout the model; i.e. the behavior you are seeing.
A second related question: why are you background fetching all of the images instead of lazy loading them from the Address Book? This looks like a case of premature optimization to me.
Andrew
Using NSXMLParser to get data from an xml file on internet with an update button to get update database and store it by using xcode template core data.
However, when I updated all core data, console can only show right data until I quited the app and run it again.
Any way to update all data correctly without quiting the app?
Thanks for that answer.
With using Xcode template (Navigation-based Application with core data), fetchedResultsController and managedObjectContext and so on are all included. Yet, every time I changed xml file and run parser to store data again, both my table view and nslog messege come up with a list of wrong data, eg, list of numbers only while my data contains lots of strings.
Display comes back to normal only I killed and rerun app again.
Any ideas?
Using CoreData the simplest thing to do is to use a NSFetchedResultsController. You can set your view controller to be the delegate for the NSFetchedResultsController, which will give you notifications when your data changes. You can then update your table appropriately.
This tutorial might help: http://www.raywenderlich.com/999/core-data-tutorial-how-to-use-nsfetchedresultscontrolleY