Good evening!
So I've been having some trouble understanding what the hell is going on while saving my data in Core Data. First of all, a quick question:
1) When should I be using the persistentStoreManagedObjectContext and when should I be using the mainQueueManagedObjectContext?! Right now, I always use the persistentManagedObjectContext, but I can see that a RestKit call "getObjectsPath", the object will have the mainQueueObjectContext. Why is that?!
Thanks!
persistentStoreManagedObjectContext could be used to populate other another NSManagedObjectContext (e.g. for a background thread).
So unless your not leaving the main thread when accessing the object, use the mainQueueManagedObjectContext and you're on the safe site.
Related
I am trying to understand a concept of backgroundContext in CoreData. Though I read several articles about it, I am not still sure about its purpose.
I have an app using CoreData that allows user create, update or delete records. User can also fetch the data he added. I want to ensure that if there are a lot of records, it will not influence a flow of the UI while fetching data.
So I studied a bit about backgroundContexts. I implemented following according to what I understood. I am not sure though whether it is a correct solution. My idea is - if I fetch in background, it cannot influence the main thread.
//I have an PersistentContainer created by Xcode.
//I create an backgroundContext.
self.backContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.newBackgroundContext()
//Then if the user adds a new record, it's added to backContext and saved to mainContext
...
let newRecord = Record(context: self.backContext!)
...
self.backContext!.save()
self.context!.save() // mainContext
...
//If the user fetches the data I use:
self.backContext.perform {
...
}
//Since I want to show results in UI, I know these objects (from fetch) exist just in the background thread, so instead I fetch for IDs
.resultType = .managedObjectIDResultType
//Now I have IDs of fetched objects, so I restore objects in main thread using their IDs:
let object = try? self.backContext.existingObject(with: ID) as? Record
//and I can finally use fetched objects to update UI.
The question is:
Is this even correct what I am doing? (it works perfectly though)
Will this solve the problem of freezing UI if user fetches a large amount of data?
How do we use backgroundContexts correctly? Why is it not recommended to work directly with mainContext? How to prevent freezing UI while fetching big data?
One more question: If I use FetchedResultsConteroller - do I need to handle the problem of freezing UI? (while waiting on first (init) fetch result?)
Of course I am ignoring that while fetching data, my context is blocked, so I cannot create a new record
If you are fetching objects to be displayed on screen, you should absolutely be using fetch requests against the main thread context. Core Data is designed for this specific use case and you should not be experiencing slowdowns or freezes because of executing fetches. If you are having problems, then profile your app in instruments and find out where the actual slowdown is.
Background contexts are meant to be used if you are performing bulky or long-running work like processing large API responses which you've shown to be affecting main thread performance.
So I do not have to be afraid of freezing UI, even if my database will contain thousands of records? I can make fetch request with mainContext?
Yes
If I would like to do some special time consuming operations that would not be shown to UI, my code would be correct, right?
Yes, you'd normally create a background context, do work, save the background context - and then access those objects as normal from the main context.
And last but not least - why is it not recommended to work directly with mainContext when I add a new record?
I'm not sure where you've seen this recommendation, but quite a common pattern is to make a new main-queue (not background) child context to support the application workflow of adding a new object. Then if the user cancels the addition, you can just discard the editing context without needing to worry about undoing your work.
I'm building a tab bar application for iPhone and i'm using Core Data with two UIManagedDocuments. In the first tab, i write the data to database and in the second i read them into UITableView with UIFetchedResultsController.
At the start of application, if i write data first, and after then i read results, it works fine. Results appear in the second tab immediately. However, if i read some data first and after then if i write something to database, results appear in second tab with considerable delay (almost 1 minutes). If is there any synchronization problem between two UIManagedObjectContexts or two UIManagedDocuments, how does it works in the first condition? And, is there any solution for this delay?
The way that you can ensure that your UIManagedDocument is up to date is to make sure you're saving your changes properly. Given the information you've shown above, I'm not really sure about how you're managing your documents or your managedObjectContexts. There are just too many factors that could be affecting this to be able to give you a 100% concrete answer.
So without knowing what your code looks like and without knowing how you're managing your context, the only thing I can do is give you what I use in my own projects. This may or may not help you, but it helped me - more times than one - when it comes to handing core data by UIManagedDocument.
When it comes to Context:
I use a singleton to manage UIManagedDocument. I do this because I don't want to have to deal with what you're talking about above - having more than one managedObjectContext. When you start dealing with multiple contexts, you have the issue where the data will not be consistent unless you manage all of your contexts properly. If you save on one but don't update the other - then your data can become out of sync. You also have to make sure that each context is working on the property thread - the Apple Docs is a great resource for understanding the whys ad hows this even matters.
The point is, though, that this is one of the biggest problems with working with UIManagedDocument that isn't as bad as when you're working with pure core data and using a SQL persistent store. The main reason that I've found is because of how UIManagedDocument actually saves to its UIDocument store. It is very unpredictable about when it wants to save. This makes knowing when your UIManagedDocument will actually persist and have your data a freaking shot in the dark. You end having to do all kinds of stupid stuff just to make sure that it is always readily available.
Considering I have a belief (that many, maybe rightfully so, believe is an ignorant belief) that working with core data is hard, and UIManagedDocument makes it easier than it would be if you didn't work with it at all. That being said, I don't like it when working with something as simple as UIManagedDocument begins to get complicated - so I use the one thing that has always kept it simple, and that is a singleton, shared-instance of a single UIManagedDocument so that I only have 1 managedObjectContext, ever, to have to work with.
When it comes to saving:
Whenever I make any significant change to a model ( Create, Update, Delete, Edit ), I always make sure to call [document updateChangeCount:UIDocumentChangeDone]; I do this because I do not use the "undo manager" (NSUndoManager) when working with UIManagedDocument. Simply because I haven't needed to yet, plus because I hate all the "workaround" garbage you have to do with it.
Working only on the Main Thread:
Whenever I do anything with my UIManagedDocument or Core Data, I always make sure its on the main thread. I think I've already said it once, but I'll say it again: working in threads is helpful when you need it, but also when you actually understand threading in general. I like working in threads, but it comes at a cost of complexity that makes me not want to work in them when it comes to core data. With that being the case, I tend to stay strictly on the main thread as this keeps things simple and easy (for me).
Saving the Document
When I absolutely need to make sure that the UIManagedDocument is "saved" ( written to disk ), I have 2 methods that I wrote and use that are always readily available for me to call: saveDocument and forceSaveDocument.
The first one ( saveDocument ) merely checks the context for changes. If it has any, it then checks to see if we have any newly inserted objects. When insertedObjects are found, it obtains the perm ids for these items. You can think of this one as a good way to ensure that your core data model is up to date, and that your managed context is in a safe state, so that when your document is actually saved, that you get everything saved in the state that it needs to be in (your ids are realized, your contexts are clean, and what you are about to save represents a state of your model once all work has been complete on it).
Its big brother, forceSaveDocument, actually calls saveDocument first. Again, to make sure that your actual model/context is saved and proper. If it returns successful ( YES ), then it will actually do the real saving and write the document to disk by means of saveToUrl.
Some Code (hopefully it helps):
Here are those 2 methods in case it helps:
-(BOOL)saveDocument {
NSManagedObjectContext *context = self.document.managedObjectContext;
if(!context.hasChanges) return YES;
NSSet *inserts = [context insertedObjects];
if([inserts count] > 0) {
NSError *error = nil;
if(![context obtainPermanentIDsForObjects:[inserts allObjects] error:&error] && error) {
[self reportError:error];
return NO;
}
}
return YES;
}
-(void)forceSaveDocument {
if( [self saveDocument] ) {
[self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:self.onSaveBlock ? self.onSaveBlock : nil];
}
}
General Rules/Guidelines
Overall, these are my guidelines that I follow ( and have worked for me for about 3 years now ) when working with UIManagedDocument and Core Data. I'm sure there are better out there from guys/gals much smarter than me, but these have what I use. The benefit I get out of them is that it makes me have to worry less about managing my data and gives me more freedom to work with everything else:
Use a singleton to manage my UIManagedDocument until the need of multiple threads is absolutely necessary - then migrate over to start using multiple contexts ( i've never needed to do this yet - but then again I try to keep things simple )
Always call updateChangeCount:UIDocumentChangeDone when I make any change to a model. It is very light weight and has little impact. If anything, it will help ensure your document stays up to date and never gets too out of sync with your data.
Don't use undo manager unless you actually need it ( I have yet to need it )
Use save/ForceSave sparingly, and only when absolutely necessary (deletes are a good reason to use it. Or if you create a new item on one view controller and need it on the next one, but can't wait for core data and the document to sync up - its kind of like kicking it in the ass and saying "I object to you saving whenever you want - save now lol.. )
Final Thoughts
All of the above is my own belief and understandings. These come from a lot of research, reading, and being a pain the ass when it comes to wanting to do things right, all while keeping it simple. Anyone can write a complex solution - but I think the fundamental question is always: do you really need the complexity, or do you just need it to work so you can focus on more complex issues?
I'm sure the above is way more than you probably wanted, and may even add more questions than you have. If you need some links and resources, let me know and I'll try to throw a few together.
Either way, hope that helps.
I'm looking to integrate iCloud with a Core-Data-managed SQLite database (only on iOS 7 and later). I've been reading Apple's guide on using Core Data with iCloud (https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingCoreDataWithiCloudPG.pdf).
To quote from the guide, "Core Data posts an NSPersistentStoreCoordinatorStoresWillChangeNotification notification. In your notification handler, you reset your managed object context and drop any references to existing managed objects."
Calling -reset on the MOC to reset it isn't the problem, the problem is the part where they say all references to managed objects need to be dropped. I understand why this needs to be done (because the persistent store is changing), what I don't know is how to do it.
All my Core Data work is handled by a singleton and I had originally thought of posting a notification, and listening classes could set all their managed objects to nil. First, this doesn't sound like a particularly good way of doing it. Secondly, I have a FetchedResultsController managing a tableView, the FetchedResultsController manages it's own managed objects, therefore, as far as I know, I can't set them to nil.
I'd be really grateful for any advice on what to do here.
Thanks in advance.
The way I handle situations like this is to post two notifications in my app: just before resetting, and just after resetting.
For example, I might post MYMainContextWillResetNotification, then reset the context, then post MYMainContextDidResetNotification.
Any controller receiving the will-reset notification should release its managed objects, but also store any information it will need to recover after the reset. Usually this will be one or more NSManagedObjectID objects. In some cases, you may not need to store anything, simply performing a fetch after the reset instead.
A typical method might look like this:
- (void)mainContextWillReset:(NSNotification *)notif
{
self->noteID = note.objectID;
}
This code supposes there is a controller for a single note object. When the reset is about to take place, the note's object identifier is stored in an instance variable.
The did-reset notification method retrieves the note.
- (void)mainContextDidReset:(NSNotification *)notif
{
note = [context existingObjectWithID:noteID error:NULL];
[self refreshViews];
}
This code uses existingObjectWithID:error:, but you could equally do a fetch.
With an NSFetchedResultsController, you would need to call performFetch: in the did-reset method, to refresh the objects.
Preamble: I'm using RestKit 0.21.0, but I've also tried 0.23.0.
I have a number of mappings where a plain old Objective-C object (let's call that a POOCO) contains an NSManagedObject. It seems that when doing GET requests on this root POOCO, the managed object is created under the mainQueueManagedObjectContext.
However when I do a PUT against the POOCO, when the response is loaded (the response is a copy of the object that was sent), the managed sub-object is created under a temporary NSManagedObjectContext, whose parent is the mainQueueManagedObjectContext.
A quick aside on re-fetching in RestKit:
If the request were composed entirely of NSManagedObject instances, the resulting objects would be "refetched" using (I think) the mainQueueManagedObjectContext. (IIRC, once all the object mapping is complete, the objects and sub-objects would all be re-fetched from the main MOC, replacing the objects that were created on the temporary MOC used for mapping.)
This refetching is done (as far as I can tell) because when a MOC gets dealloc'd, all of the managed objects that it manages become invalid. So we refetch the objects in a MOC to which myObjectManager.managedObjectStore maintains a strong reference, so that they remain valid after the temporary MOC goes away.
Because the root object in the mapping is a POOCO, any references it has to managed objects do not get refetched, so they remain attached to the temporary MOC, and become invalid once mapping is complete.
Has anyone else run into issues like this? Are there best practices you can suggest? Is it possible to tell RestKit, "refetch these managed objects using the mainQueueManagedObjectContext" or anything like that?
Managed object instances can't be passed between threads, and RestKit does the mapping on background threads. If the returned objects are managed objects then they will be transferred to the main thread for you. It could be argued that nested managed objects not being switched to the main thread for you is a bug / oversight in RestKit (that you could raise as an issue in github).
You can ask RestKit to use the main thread for some things (by explicitly creating the operations and running them, though this doesn't cover everything) but you really don't want to do that.
So, your main option is to have a method in your container object which you can call in the success block and which iterates through the contained managed objects and 'refetches' them - as you know that all the objects exist this can be done with objectWithID:, and the success block is called on the main thread.
The question actually has some misconceptions about when RestKit performs refetching. Under the circumstances described in the question, RestKit actually does return an instance of RKRefetchingMappingResult in the success block. Calling any of the accessors on that object (such as array or firstObject) actually does perform the refetching. The mistake was that I was never invoking any of these methods on the RKMappingResult.
My assumption was that, since the object I was sending in the PUT request would become the operation's targetObject, the object would be updated in-place by RestKit. This is why I never felt the need to do anything with the RKMappingResult. The assumption isn't technically wrong, but by accessing the object directly once the mapping was complete instead of using the RKMappingResult, I was skipping the refetching step.
I've proposed a change in this behaviour in an issue I've filed with the RestKit folks, to make refetching automatic in certain circumstances.
To summarize:
When making a request using RKManagedObjectRequestOperation (whether you specify that manually or RestKit selects this class for you), always make sure you exercise the RKMappingResult to ensure that the results are re-fetched from the correct NSManagedObjectContext.
I am using AFNetworking to fetch JSON objects from the server. I then use ObjectiveRecord to create data object (Core Data). Sometimes I search and update objects. This works well in the simulator on, as well on my iPhone 5. However when using an iPhone 5S it does not work. I get a EXC_BAD_ACCESS when doing an insertNewObjectForEntityForName (in ObjectiveRecord code).
Reading a lot of articles about Core Data and threading, this has led me to believe that I should use private context, using NSPrivateQueueConcurrencyType.
I use the suggested way to set this in ObjectiveRecord, that is:
NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
newContext.persistentStoreCoordinator = [[CoreDataManager instance] persistentStoreCoordinator];
I then use createInContext: to create my objects in the completion block of AFNetworking.
I have found a similar question on SO, but it does not use ObjectiveRecord: Get a number of resources asynchronously and "asynchronously" save them to a database. Which good pattern to use? (AFNetworking, Core Data)
However I do not get it to work. What am I doing wrong? Or am I on the wrong track?
I did not get this to work. Probably because my skills in Core Data was not enough. Therefore I switched to MagicalRecord instead. It was easier to understand how to use it in a multithreaded environment than ObjectiveRecord. It´s a shame that there is no good article on how to use ObjectiveRecord in a multithreaded environment.