Multithreaded Core Data object has empty properties on main thread after saving on background thread
The problem, I think, is that Core Data is not thread safe. I know you know this - that, after all, is why you created a special background context - but it has very strict implications. You are saying:
dataManager.persistentContainer.performBackgroundTask { context in
context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
let user = self.userFactory.user(fromJSON: json, inContext: context)
do {
try context.save()
DispatchQueue.main.async {
print(user) // <--
You can't do that. You're still talking about the user
you saved into the background context, but you've switched threads. No. You cannot talk about this user
from two different threads.
Once you are on the main thread, the only way you may talk to Core Data is through the main context. You need to fetch the user out, by its id, from the main context. Now you can look to see how it is populated.
Is it possible to use background thread on NSFetchedResultsController for heavy read operation to ensure UI responsiveness?
I think it is possible that your problem is the assignment of the fetchedResultsControllerDelegate
by the new controller. This seems to be inferable from where you encounter the multithreading violation.
From the code you provided, it is not clear what kind of object the delegate is, but presumably it manages the UI and thus is on the main thread. You can see who this can potentially lead to a threading problem.
Thanks for posting a link to this question from my answer here https://stackoverflow.com/a/14710475/427083. I still think that it should work for you if you assign the FRC as the datasource
of you table once it is finished, and then reloadData
.
On a side note: while I find this problem interesting and challenging from an engineering point of view, I doubt that it is good architecture to deal with millions of records directly on a mobile device.
Fetching Core Data objects in the background: objects not faulted
Why not take the easy route, since you are not saving anything new ?
Instead of creating an extra context for the background thread and working with IDs, use the main managedObjectContext
in the background thread after locking it.
for example:
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *context = ... // get the main context.
[context lock]; // lock the context.
// do something with the context as if it were on the main thread.
[context unlock]; // unlock the context.
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
This works for me when I call a method with performSelectorInBackground
, so I guess it should work for GCD dispatch too.
Fetch related Core Data objects in background? (to prevent UI freeze)
Do you know for sure that it is your main thread that is causing the slowdown (sure sounds like it) - I'd use Instruments and "Time Profiler" to be sure, and there is also a way to turn on SQL debugging/timing too.
If it is your main thread, there are fantastic WWDC videos (take a look at 2010 too, not just 2011) on how to optimize Core Data.
I insert my CoreData in background Context and when I fetch, it doesn't show the data
The mistake you are making is to not test the many little pieces of this code which could be not working as expected. To troubleshoot something like this, you must start at the beginning and test one piece at a time. The first issue I see is confusion between managed object contexts…
It appears that the method
saveManagedObjectContext
is a getter for your main managed object context. In English, save is a verb, so that this method name implies that it is saving some managed object context. If I am correct, you should change the namesaveManagedObjectContext
to maybemainMangaedObjectContext
.The statement
_managedObjectContext = [appDel managedObjectContext]
is quite nonstandard, assigning to an instance variable from what appears to be its getter. If, as usual,_managedObjectContext
is the instance variable backing the propertymanagedObjectContext
, this statement has no effect.In your first method you use
[appDel saveManagedObjectContext]
, and in your second method you use[appDel managedObjectContext]
. It looks like these should be the same context in order for your fetch to work. Are they?
UPDATE:
As you stated in your comment, that fixes the saving, but now you have a performance problem – saving to the persistent store on disk blocks the user interface, which is what your original code was trying to fix.
This is a solved problem. The solution is that your main-thread context, which interfaces the user, should be a child of your background-thread context which interfaces to the persistent store. It is explained nicely with code in this 2015 blog post by Marcus Zarra. For further reading, Chad Wilken has published a slight variation. Both are written in Objective-C for you :)
Changes not recognized my NSFetchedResultsController attached to background thread
The problem / limitation with FRC is that it only contains the entities for which it was setup. In this case:
self.fetchedResultsController = [Document MR_fetchAllSortedBy:@"timestamp"
ascending:NO
withPredicate:[NSPredicate predicateWithFormat:@"report.remoteId != nil && dirty == YES"]
groupBy:nil
delegate:self
inContext:_managedObjectContext];
the FRC will only contain Document entities. The initial fetch will work on the report relation. But if a report changes the FRC will not be updated, even though the predicate matches.
http://www.mlsite.net/blog/?p=825
and here
Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view
Core Data locks in background threads
Not totally sure if this applies to you but I was getting similar errors. I solved them by
Using
NSPrivateQueueConcurrencyType
notNSConfinementConcurrencyType
to take processing off the main thread.Putting
executeFetchRequest
inside MOC'sperformBlockAndWait
.
So in CoreDataHelper.m's fetchEntity method you would have something like this:
[context performBlockAndWait:^{
NSError *error = nil;
NSArray *entities = [context executeFetchRequest:request error:&error];
}];
Related Topics
Swift Cannot Output When Using Nstimer
Swiftui Tabview Gives an Error Message During Add/Delete the Element of Coredata
How to Unpack Multiple Levels of Nested JSON in Firebase Database
Cfdictionary Get Value for Key in Swift3
Ordering Firebase Posts Chronologically Swift
Cicolorcontrols & Uislider W/ Swift 4
Convert from Utc to Local Timezone Give Wrong Result
Generate Labels and Align in Middle
How to Sleep for Few Milliseconds in Swift 2.2
Switch Statement for Imported Ns_Options (Rawoptionsettype) in Swift
Swift Decodable, Endpoint Returns Completely Different Types
Swift: Nil Error When Using Self.Moc.Save() to Save in Core Data
How to Sort an Array of Posts by Their Elements
Get All Documents at Once in a Completion Handler with Getdocuments in Firestore