I have a CoreData stack that is like this:
Persistent Store -> (private)writingContext -> (main)UIContext
-> (private)backgroundContext
This is a commonly suggested stack to use in various blogs and people who have mastered CoreData, except that others will have the backgroundContext a child of the main context. But thats not the issue I am seeing. What I have is
[[self masterManagedObjectContext] performBlock:^{
NSError *error = nil;
BOOL saved = [self.masterManagedObjectContext save:&error];
if (!saved) {
// do some real error handling
[EventLogger logError:#"CoreDataService" message:#"masterContext save ERROR" error:error];
}
}];
Now this is executed from a UIContext performBlock calls this master save within a block and because its not blocking the next code to run is a tableView reload where it gets the fetched objects, accesses the properties and puts them on the tableViewCells. I am getting a deadlock here because the save is still running but the UIContext is accessing the property values which are going into the master to pull the values into memory. This happens consistently.
From what I understand, the contexts don't operate but on a single thread with a queue, well with the UI being a child of the master if the master is doing something and the child is requesting something at the same time, its causing the deadlock. How do I avoid this? How can I async save whats in the UI and not deadlock on accessing a property of an NSManagedObject?
Related
I use multiple contexts in my Core Data app, and have recently had some core data concurrency crashes. I have added -com.apple.CoreData.ConcurrencyDebug 1 to help track these down, but I am not understanding how to fix the issue that is shown.
Here is what I am doing:
- (void)getEvents:(void (^)(NSArray *fetchedItems))completionBlock {
// Initialize Fetch Request
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"ZSSCDEvent"];
// Initialize Asynchronous Fetch Request
NSAsynchronousFetchRequest *asynchronousFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:request completionBlock:^(NSAsynchronousFetchResult *result) {
dispatch_async(dispatch_get_main_queue(), ^{
// Dismiss Progress HUD
[SVProgressHUD dismiss];
// Process Asynchronous Fetch Result
if (result.finalResult) {
NSArray *results = result.finalResult;
completionBlock(results);
// Reload Table View
[self.activityIndicator stopAnimating];
[self.tableViewList reloadData];
}
});
}];
// Execute Asynchronous Fetch Request
[self.managedObjectContext performBlock:^{
// Execute Asynchronous Fetch Request
NSError *asynchronousFetchRequestError = nil;
NSAsynchronousFetchResult *asynchronousFetchResult = (NSAsynchronousFetchResult *)[self.managedObjectContext executeRequest:asynchronousFetchRequest error:&asynchronousFetchRequestError];
if (asynchronousFetchRequestError) {
NSLog(#"Unable to execute asynchronous fetch result.");
NSLog(#"%#, %#", asynchronousFetchRequestError, asynchronousFetchRequestError.localizedDescription);
}
}];
}
This gives me a Enqueued from com.apple.main-thread (Thread 1) error. This is where I am confused, since I am running this on the main thread and didn't think I needed to use my private context here.
Any ideas on why I am getting a concurrency issue here?
EDIT: It looks like someone else had the exact issue and thinks it is an Xcode bug: CoreData asynchronous fetch causes concurrency debugger error
Every managedObject has a context. Each context has one and only one thread that it can run on. ManagedObjects are NOT thread safe - not even for reading. Passing managedObjects around with completions blocks is a bad practice. It can be hard to figure out which managedObjects are supposed to be on which thread. Also, even if you are passing it around on the correct thread it is still a bad practice. When you do a dispatch_async the entity may have deleted from the database in the interim and accessing the managedObject will cause a crash. A good practice is that any method that does a fetch should be explicitly told which context to use and return synchronously. In your code the method is using self.managedObjectContext, but I have no way to know looking at you code what thread that is related to. Furthermore the pointer to the context may change and that can cause bugs that are very hard to track down.
NSAsynchronousFetchResult contains managedObjects, so is not thread safe and can only be used inside that completion block (which is running on the correct thread for the objects). You cannot pass them to another thread. If you return them in a block then that code must also not pass them to another thread. You should just do whatever you need to do with them inside the block and then discard them.
If you need to display the information to the user, then generally it is better to just do the fetch on the main thread synchronously and get managedObjects that are associated with a main thread context. If your fetch is taking to long then you should fix that - there is no reason for a fetch to take so long. In your case you appear to be fetching ALL the items in you database. That is a mistake. You should use a predicate to only get the ones that you need.
Another possibility is to copy the values of managedObjects into a thread safe object (a dictionary or a NSObject subclass).
TL;DR You probably don't need a NSAsynchronousFetchRequest. Use a regular fetch request on the main thread and return synchronously. Use a predicate to limit the results only to the objects you are actually displaying.
Background
Saving a large amount of data at a time is very slow.
Current Setup
In my app there's a private-queue NSManagedObjectContext as the parent that talks to the NSPersistentStoreCoordinator directly to save data. A child main-queue context is consumed by a NSTreeController for the UI(NSOutlineView).
(My goal was to prevent any occurence of the beach ball. Currently I remedy the problem by only saving data when the app goes inactive. But since the data that are planed to be deleted are not deleted yet, they may still come up in a fetch result. That's another problem I'm trying to solve.)
The Problem
The child main-queue context can only wait when fetching when the parent context is busy saving.
Related Problems
Core Data: parent context blocks child This perhaps is essentially the same question. I've noticed the answer says the setup is intrinsically wrong. Then I wonder is there a way to do the writing and reading at the same time with Core Data?
Correct implementation of parent/child NSManagedObjectContext A commonly asked question lacks answers with insights (sorry...).
What is the most efficient way to delete a large number (10.000+) objects in Core Data? If the objects can't be designated by a NSPredicate, we still need to rely on the traditional delete() (and maybe the Cascade delete rule) Also, it doesn't eliminate the blocking-the-UI issue.
I will update this question when I have more findings.
I'm guessing you're developing for OS X / macOS (NSTreeController & NSOutlineView). I've no experience with macOS - I develop for iOS - so you might need to take that into account when you're reading my response.
I've not yet made the switch to swift - my code is, perhaps obviously, Objective-C...
I'll start with how I prepare the Core Data stack.
I set up two public properties in the header file:
#property (nonatomic, strong) NSManagedObjectContext *mocPrivate;
#property (nonatomic, strong) NSManagedObjectContext *mocMain;
Although this is unnecessary, I also prefer to set up private properties for my Core Data objects, including, for example:
#property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
Once I've pointed to my model URL, established my managed object model NSManagedObjectModel, pointed to my store URL for my NSPersistentStore and established my persistent store coordinator NSPersistentStoreCoordinator (PSC), I set up my two managed object contexts (MOC).
Within the method to "build" my Core Data stack, after I've completed the code per the above paragraph, I then include the following...
if (!self.mocPrivate) {
self.mocPrivate = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.mocPrivate setPersistentStoreCoordinator:self.persistentStoreCoordinator];
} else {
// report to console the use of existing MOC
}
if (!self.mocMain) {
self.mocMain = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[self.mocMain setParentContext:self.mocPrivate];
} else {
// report to console the use of existing MOC
}
(I usually include a few NSLog lines in this code to report to my console but I've excluded that here to keep the code clean.)
Note two important aspects to this code...
set the private queue MOC to interact with the PSC; and
set the main queue MOC as the child of the private queue MOC.
Why is this done? First let's highlight a couple of important points:
Saves to memory are relatively fast; and
Saves to disc are relatively slow.
The private queue is asynchronous to the main queue. The User Interface (UI) operates on the main queue. The private queue operates on a separate thread "in the background" working to maintain context and coordinate data persistence with the PSC, perfectly managed by Core Data and iOS. The main queue operates on the main thread with the UI.
Written a different way...
Heavy work completing irregular (managed by Core Data) data persistence to the PSC (saves to disc) is completed in the private queue; and
Light work completing regular (managed by developer) data persistence to the MOC (saves to memory) is completed in the main queue.
In theory this should ensure your UI is never blocked.
But there is more to this solution. How we manage the "save" process is important...
I write a public method:
- (void)saveContextAndWait:(BOOL)wait;
I call this method from any class that needs to persist data. The code for this public method:
- (void)saveContextAndWait:(BOOL)wait {
// 1. First
if ([self.mocMain hasChanges]) {
// 2. Second
[self.mocMain performBlockAndWait:^{
NSError __autoreleasing *error;
BOOL success;
if (!(success = [self.mocMain save:&error])) {
// error handling
} else {
// report success to the console
}
}];
} else {
NSLog(#"%# - %# - CORE DATA - reports no changes to managedObjectContext MAIN_", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
}
// 3. Third
void (^savePrivate) (void) = ^{
NSError __autoreleasing *error;
BOOL success;
if (!(success = [self.mocPrivate save:&error])) {
// error handling
} else {
// report success to the console
}
};
// 4. Fourth
if ([self.mocPrivate hasChanges]) {
// 5. Fifth
if (wait) {
[self.mocPrivate performBlockAndWait:savePrivate];
} else {
[self.mocPrivate performBlock:savePrivate];
}
} else {
NSLog(#"%# - %# - CORE DATA - reports no changes to managedObjectContext PRIVATE_", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
}
}
So I'll work through this to explain what is happening.
I offer the developer the option to save and wait (block), and depending on the developer's use of the method saveContextAndWait:wait, the private queue MOC "saves" using either:
the performBlockAndWait method (developer calls method with wait = TRUE or YES); or
the performBlock method (developer calls method with wait = FALSE or NO).
First, the method checks whether there are any changes to the main queue MOC. Let's not do any work unless we have to!
Second, the method completes a (synchronous) call to performBlockAndWait on the main queue MOC. This performs the call to save method in a code block and waits for completion before allowing the code to continue. Remember this is for regular "saves" of small data sets. The (asynchronous) option to call performBlock is not required here and in fact will derail the effectiveness of the method, as I experienced when I was learning to implement this in my code (failure to persist data due to the save call on the main queue MOC attempting to complete after completion of the save on the private queue MOC).
Third, we write a little block within a block that contains the code to save the private queue MOC.
Fourth, the method checks whether there are any changes to the private queue MOC. This may be unnecessary but it is harmless to include here.
Fifth, depending on the option the developer chooses to implement (wait = YES or NO) the method calls either performBlockAndWait or performBlock on the block within a block (under third above).
In this last step, regardless of the implementation (wait = YES or NO), the function of persisting data to disc, from the private queue MOC to the PSC, is abstracted to the private queue on an asynchronous thread to the main thread. In theory the "save to disc" via the PSC can take as long as it likes because it has nothing to do with the main thread. AND because the private queue MOC has all the data in memory, the main queue MOC is fully and automatically informed of the changes because it is the child of the private queue MOC.
If you import large volumes of data into app, something I am currently working on implementing, then it makes sense to import this data into the private queue MOC.
The private queue MOC does two things here:
It coordinates data persistence (to disc) with the PSC;
Because it is the parent of the main queue MOC (in memory), the main queue MOC will be notified of the data changes in the private queue MOC and merges are managed by Core Data;
Finally, I use NSFetchedResultsController (FRC) to manage my data fetches, which are all completed against the main queue MOC. This maintains data hierarchy. As changes are made to the data sets in either context, the FRC updates the view.
This solution is simple! (Once I spent weeks wrangling my head around it and another few weeks refining my code.)
There is no requirement to monitor notifications for merges or other changes to MOC. Core Data and iOS handle everything in the background.
So if this doesn't work for you - let me know - I may have excluded or overlooked something as I wrote this code well over a year ago.
Question: How do I get my child context to see changes persisted on the parent context so that they trigger my NSFetchedResultsController to update the UI?
Here's the setup:
You've got an app that downloads and adds lots of XML data (about 2 million records, each roughly the size of a normal paragraph of text) The .sqlite file becomes about 500 MB in size. Adding this content into Core Data takes time, but you want the user to be able to use the app while the data loads into the data store incrementally. It's got to be invisible and imperceptible to the user that large amounts of data are being moved around, so no hangs, no jitters: scrolls like butter. Still, the app is more useful, the more data is added to it, so we can't wait forever for the data to be added to the Core Data store. In code this means I'd really like to avoid code like this in the import code:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
The app is iOS 5 only so the slowest device it needs to support is an iPhone 3GS.
Here are the resources I've used so far to develop my current solution:
Apple's Core Data Programming Guide: Efficiently Importing Data
Use Autorelease Pools to keep the memory down
Relationships Cost. Import flat, then patch up relationships at the end
Don't query if you can help it, it slows things down in an O(n^2) manner
Import in Batches: save, reset, drain and repeat
Turn off the Undo Manager on import
iDeveloper TV - Core Data Performance
Use 3 Contexts: Master, Main and Confinement context types
iDeveloper TV - Core Data for Mac, iPhone & iPad Update
Running saves on other queues with performBlock makes things fast.
Encryption slows things down, turn it off if you can.
Importing and Displaying Large Data Sets in Core Data by Marcus Zarra
You can slow down the import by giving time to the current run loop,
so things feel smooth to the user.
Sample Code proves that it is possible to do large imports and keep the UI responsive, but not as fast as with 3 contexts and async saving to disk.
My Current Solution
I've got 3 instances of NSManagedObjectContext:
masterManagedObjectContext - This is the context that has the NSPersistentStoreCoordinator and is responsible for saving to disk. I do this so my saves can be asynchronous and therefore very fast. I create it on launch like this:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - This is the context the UI uses everywhere. It is a child of the masterManagedObjectContext. I create it like this:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - This context is created in my NSOperation subclass that is responsible for importing the XML data into Core Data. I create it in the operation's main method and link it to the master context there.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
This actually works very, VERY fast. Just by doing this 3 context setup I was able to improve my import speed by over 10x! Honestly, this is hard to believe. (This basic design should be part of the standard Core Data template...)
During the import process I save 2 different ways. Every 1000 items I save on the background context:
BOOL saveSuccess = [backgroundContext save:&error];
Then at the end of the import process, I save on the master/parent context which, ostensibly, pushes modifications out to the other child contexts including the main context:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Problem: The problem is that my UI will not update until I reload the view.
I have a simple UIViewController with a UITableView that is being fed data using a NSFetchedResultsController. When the Import process completes, the NSFetchedResultsController see's no changes from the parent/master context and so the UI doesn't automatically update like I'm used to seeing. If I pop the UIViewController off the stack and load it again all the data is there.
Question: How do I get my child context to see changes persisted on the parent context so that they trigger my NSFetchedResultsController to update the UI?
I have tried the following which just hangs the app:
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == mainManagedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
You should probably save the master MOC in strides as well. No sense having that MOC wait until the end to save. It has its own thread, and it will help keep memory down as well.
You wrote:
Then at the end of the import process, I save on the master/parent
context which, ostensibly, pushes modifications out to the other child
contexts including the main context:
In your configuration, you have two children (the main MOC and the background MOC), both parented to the "master."
When you save on a child, it pushes the changes up into the parent. Other children of that MOC will see the data the next time they perform a fetch... they are not explicitly notified.
So, when BG saves, its data is pushed to MASTER. Note, however, that none of this data is on disk until MASTER saves. Furthermore, any new items will not get permanent IDs until the MASTER saves to disk.
In your scenario, you are pulling the data into the MAIN MOC by merging from the MASTER save during the DidSave notification.
That should work, so I'm curious as to where it is "hung." I will note, that you are not running on the main MOC thread in the canonical way (at least not for iOS 5).
Also, you probably only are interested in merging changes from the master MOC (though your registration looks like it is only for that anyway). If I were to use the update-on-did-save-notification, I'd do this...
- (void)contextChanged:(NSNotification*)notification {
// Only interested in merging from master into main.
if ([notification object] != masterManagedObjectContext) return;
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
// NOTE: our MOC should not be updated, but we need to reload the data as well
}];
}
Now, for what may be your real issue regarding the hang... you show two different calls to save on the master. the first is well protected in its own performBlock, but the second is not (though you may be calling saveMasterContext in a performBlock...
However, I'd also change this code...
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
// Make sure the master runs in it's own thread...
[masterManagedObjectContext performBlock:^{
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
// Handle error...
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}];
}
However, note that the MAIN is a child of MASTER. So, it should not have to merge the changes. Instead, just watch for the DidSave on the master, and just refetch! The data is sitting in your parent already, just waiting for you to ask for it. That's one of the benefits of having the data in the parent in the first place.
Another alternative to consider (and I'd be interested to hear about your results -- that's a lot of data)...
Instead of making the background MOC a child of the MASTER, make it a child of the MAIN.
Get this. Every time the BG saves, it automatically gets pushed into the MAIN. Now, the MAIN has to call save, and then the master has to call save, but all those are doing is moving pointers... until the master saves to disk.
The beauty of that method is that the data goes from the background MOC straight into your applications MOC (then passes through to get saved).
There is some penalty for the pass-through, but all the heavy lifting gets done in the MASTER when it hits the disk. And if you kick those saves on the master with performBlock, then main thread just sends off the request, and returns immediately.
Please let me know how it goes!
So the objective I'm trying to achieve
is a syncing process that is supposed to be completed in the background using AFNetworking and Magical Record, but causes a perpetual hang when a view controller hooked up to a NSFetchedResultsController is currently open or has been opened (but popped).
The app syncs the first time the user opens the phone and then uses the data always in the Core Data persistance store via the Magical Record framework. Then when the user wants to make sure that there data is the most recent version, they go into settings and click "Re-Sync", which causes the following code to execute:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[[CoreDataOperator sharedOperator] commenceSync:self];
});
This starts the syncing process using the singleton CoreDataOperator (subclass of NSObject -- maybe it should be NSOperation?), which fires off the following code:
[[ApiClient sharedClient] getDataRequest];
which then then fires off this bad boy in the singleton AFHTTPClient subclass:
[[ApiClient sharedClient] postPath:url parameters:dict
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[request.sender performSelector:request.succeeded withObject:response];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[request.sender performSelector:request.failed withObject:response];
}
];
which effectively says: AFHTTPClient post this infomation and when it succeeds, pass back the information to the provided selector (I understand that this is generic, but the request isn't the issue)
NOW, AFNetworking is coded such that all completion selectors (in this case specifically, success and failure) are called on the main thread; so in order to prevent blocking the main thread, the code that processes the response and prepares it for saving is sent back into the background thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
id responseData;
[self clearAndSaveGetData:responseData];
});
This then calls the function that causes the saving using the Magical Record framework (still in background thread):
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
[DATAENTITY truncateAllInContext:localContext];
<!--- PROCESS DATA INTO DATAENTITY -->
[localContext saveNestedContexts];
I chose saveNestedContexts because since I am working in the background, I wanted it pushed all the way up to the defaults contexts, which I'm assuming is a parent context? (but this so far hasn't been an issue).
Now, this data can become thousands and thousands of rows, so I'm using a NSFetchedResultsController to access this data safely and efficiently, and they are used in a different view controller than the settings or main page.
Here are three cases:
The ViewController containing the FRC has not been accessed (not visible and hasn't been visible before) - the background sync works flawlessly, minus a little lag due to the saving up the stack.
The ViewController containing the FRC has been accessed and is currently visible - background sync process HANGS due to what appears to be the FRC receiving context updates.
The ViewController containing the FRC has been previously access, but it isn't currently visible (the VC was popped using the following code: [self.navigationController popViewControllerAnimated:YES];) AND the FRC is set to nil in ViewDidUnload - background sync process HANGS due to what appears to be the FRC receiving context updates (just like in case 2).
[PS: after hanging for a few minutes, I kill the debugger and it gives my a SIGKILL on the following code, which is why I'm assuing the FRC is receiving context updates that causes it to hang:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates]; <---- SIGKILL
}
Other important information of note:
I am using 2 separate FRCs, one for normal ALL data and one for searching
I am using a cache for both the FRC and the separate search FRC, which is purged at the appropriate times (before this context update stuff)
The FRCs are fetching in the mainthread (which does cause a slight hang when there is THOUSANDS of rows of data) and I have been looking into fetching in the background, however that is currently not implemented.
The QUESTION(s):
Why is this hanging occuring, where the VC is visible or has been popped?
How can I make it such that the FRC doesn't listen for the save, but uses what it has until the save is completed and then it refreshes the data (unless this is what already occuring and causing the hanging)?
Would implementing a background fetch (because the lag when opening the VC with the FRC to access the thousands of rows of data creates a noticable lag, even with a cache and lessened predicates/section headers - of between 1 and 4 seconds) be viable? Too complex? How can it be done?
Thank you, hope I was detailed enough in my question.
I know this question is old but since this is a common gotcha with MagicalRecord maybe the following will help somebody.
The problem is caused by the fetchRequest of the FRC and the save operation deadlocking each other. The following solved it for me:
Update to the latest MagicalRecord release (2.1 at the time of writing).
Then do all your background saves using the following:
MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// create new objects, update existing objects. make sure you're only
// accessing objects inside localContext
[myObjectFromOutsideTheBlock MR_inContext:localContext]; //is your friend
}
completion:^(BOOL success, NSError *error) {
// this gets called in the main queue. safe to update UI.
}];
I am using a UIManagedDocument for core data. I have Calendar objects in my managedObjectContext that have a calendarSelected attribute. The Calendar entity has a to-many relationship to CalendarEntry.
When I change their calendarSelected attribute and then perform a NSFetchRequest to get CalendarEntry objects with the following predicate:
[NSPredicate predicateWithFormat:#"calendar.calendarSelected = YES"]
the calendar.calendarSelected does not seem to be seeing the change I made without me calling
[myManagedDocument saveToURL:myManagedDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {}];
first. I read somewhere that fetching things from the same context should honor changes made to in that context even if the changes had not been written to the persistent store. What am I missing?
Update:
It appears to be happening when the calendarEvents relationships is a fault: calendarEvents = "<relationship fault: 0x91aec90 'calendarEvents'>"; but works when the relationship is not a fault.
If the issue occurs only on newly-inserted CalendarEntry objects pointed to by the to-many relationship, then it's possible that they don't yet have permanent object ids. This is easy to debug via just dumping the object id and checking to see if it's temporary or permanent.
I've seen this happen when the containing object in the to-many relationship is retained; it seems that so long as it is retained, the to-many contained objects in the relationship never get to a point wherein they obtain permanent ids. Somewhat easy to debug by putting the application in the background and restarting it; the backgrounding will typically force the UIManagedDocument to save, and things will start working as you expect thereafter, as the CalendarEntry entities will have been assigned permanent ids and thus will become fetchable.
So far as saving the UIManagedDocument, you don't have control over when that'll happen when using a UIManagedDocument. The best thing to do is schedule a save to occur in the future, via updateChangeCount:UIDocumentChangeDone, but again, it'll happen 'soon', but not deterministically, i.e., it's not possible to know when it'll happen.
To resolve the temporary vs. permanent object id issue, if that's what you're seeing, try the following category on NSManagedObjectContext; call it after you've completed inserting new CalendarEntry objects, in order to force the issue.
// Obtain permanent ids for any objects that have been inserted into
// this context.
//
// As of this writing (iOS 5.1), we need to manually obtain permanent
// ids for any inserted objects prior to a save being performed, or any
// objects that are currently retained, that have relationships to the
// inserted objects, won't be updated with permanent ids for the related
// objects when a save is performed unless the retained object is refetched.
// For many objects, that will never occur, so we need to force the issue.
- (BOOL)obtainPermanentIDsForInsertedObjects:(NSError **)error
{
NSSet * inserts = [self insertedObjects];
if ([inserts count] == 0) return YES;
return [self obtainPermanentIDsForObjects:[inserts allObjects]
error:error];
}
Basically, after making any changes, you must either use an undo manager, or call [document updateChangeCount:UIDocumentChangeDone]; and THAT'S ALL! Any other "save" calls will just break something else down the line somewhere.
The canonical method of updating should be...
You know you are on main thread
NSManagedObjectContext *ctx = document.managedObjectContext;
// Do stuff with it
[document updateChangeCount:UIDocumentChangeDone];
You are on a different thread
[document.managedObjectContext performBlock:^{
// Do stuff with it on the main thread
[document updateChangeCount:UIDocumentChangeDone];
}];
OR
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = document.managedObjectContext;
[moc performBlock:^{
// Do stuff with it on a background thread
// Push changes up to parent (which is the document's main MOC)
// The document will know it's dirty and save changes
[moc save:&error];
}];
OR if you want to make changes in a background thread, without messing with the main document MOC, do them on the parent... the main MOC will not see them until the next fetch.
[document.parentContext.managedObjectContext performBlock:^{
// Do stuff with it on a background thread, but the main MOC does not
// see the changes until it does a fetch.
}];