Core Data: Forcing Propogation of Changes

I just encountered a situation where I was added NSManagedObject instances to a context and the data did not automatically show up in a bound NSArrayController/table view. I think there's an elusive bug to be found here, but the workaround is to do this:

NSManagedObjectContext * context; // assume this exists
NSManagedObject * newObject;

newObject = [NSEntityDescription
               insertNewObjectForEntityForName: @"Person"
               inManagedObjectContext: context];

// force updates to be sent to bound controllers

[context processPendingChanges];


The -processPendingChanges: forces the changes to be processed immediately instead at the end of the current event. Don't get trigger-happy and use it all the time, it's just a workaround if things are acting strange. Also, be aware that it could affect the behavior of automatic undo/redo.
Design Element
Core Data: Forcing Propogation of Changes
Posted Nov 18, 2005 — 7 comments below




 

Alex R — Nov 20, 05 555

I had a similar experience. With an arraycontroller bound to a treecontroller which was bound in turn to yet another arraycontroller. Looking at the resulting xml file showed that managed objects where being added to the context but they were not showing up in the table view.
Any idea what conditions cause this failure?

Scott Stevenson — Nov 21, 05 556 Scotty the Leopard

Not sure what causes it. Wish I knew!

Hannes Petri — Jun 04, 06 1348

A while ago, I tried to write a snippet-management-application, where you added pieces of text by dropping it on the dock icon. However, the text titles didn't show up in the table view, until I moved the window or something like that. A friend helped me solve it this way:

Put this in the init-method of the document:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(managedObjectContextUpdatedNotification:) name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]];

And add the method like this:

-(void)managedObjectContextUpdatedNotification:(NSNotification *)notif
{
NSDictionary *userInfo = [notif userInfo];
NSSet *inserted = [userInfo objectForKey:NSInsertedObjectsKey];
if (yourArrayController && inserted && [inserted count]) {
[yourArrayController setSelectedObjects:[inserted allObjects]];
}
}

Mike Margolis — Oct 10, 06 2026

Sorry if this is a bit late, I only saw this post today :)

For performance reasons, processPendingChanges: only gets called automatically after event processing in the runloop. It would be too expensive to call it after every single notification, timer invocation, etc. If you are changing your managed object context in response to anything but direct user input you must call processPendingChanges: manually for changes to take effect. Do not abuse this call, it can dramatically slow down your application if, for example, you call it after every single object creation, insertion, modification, or deletion... it is best to do all of your work in a batch before calling processPendingChanges:.

Some examples of when this is needed include a worker thread finishes loading some data and adds new data to the core data managed object context. (Of course, you did add that new data from the *main* runloop, correct?) , or a notification or timer gets triggered and you update some value.

The UI (and many internals of your app) will be out of sync with the expected state of the database....er... object-graph management and persistence thingamagigger (don't call it a database, the core data folks hate that :-D) until the user starts clicking around on the UI... at which point weird unexpected behavior might occur. My suggested approach would be to have the main thread load and process the data that was obtained in the [worker thread/posted notification userinfo / etc] and then have call [moc processPendingChanges:] before returning.

Hopefully this helps, it has saved my butt many times in the past and understanding why this is needed and how often to call it has helped keep my core data apps fast with a consistent 'database'.

Malcolm Hall — Jul 19, 08 6168

I am new to Cocoa but I think you guys have this wrong. You should not be adding to or updating the binding context you should be adding to the controller the context is bound to. E.g. in the case of an NSArrayController such as the CarsArrayController example in Hillegass's book. In this example imagine you had an NSTimer and wanted to add a new entity on the fire event every second. In MyDocument.m you would do:

- (id)init { self = [super init]; if (self != nil) { // initialization code // maybe this should go in awakeFromNib? timer =[ [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(myTimerFireMethod:) userInfo:nil repeats:YES] retain]; } return self; } - (void)myTimerFireMethod:(NSTimer*)theTimer{ id o = [carArrayController newObject]; [o setValue:@"Ford" forKey:@"makeModel"]; [carArrayController addObject:o]; }

and in MyDocument.h you have:

#import <Cocoa/Cocoa.h> #import "CarArrayController.h" @interface MyDocument : NSPersistentDocument { NSTimer* timer; IBOutlet carArrayController* c; } - (void)myTimerFireMethod:(NSTimer*)theTimer; @end

Finally of course you need to hook up the referencing outlet in the CarArrayController object you added in interface builder to the carArrayController field of File's Owner. So right click the Cars icon and drag from the "New Referencing Outlet" to Files Owner and click carArrayController in the pop up.

So when you add to the array controller in code the UI is instantly updated because all of the binding stuff works as normal. Please tell me if I am wrong about this.

Malcolm Hall — Jul 19, 08 6169

oops tiny mistake, you need:

IBOutlet CarArrayController* carArrayController;

Scott Stevenson — Jul 19, 08 6170 Scotty the Leopard

@Malcolm Hall: You should not be adding to or updating the binding context you should be adding to the controller the context is bound to

First, just to be clear, the phrase "binding context" can mean a lot of different things. At first, I thought you meant the data buffer that's passed in as the "context" argument in a -bind: method, but I'm pretty sure you mean the Managed Object Context that a controller is bound to.

In terms of adding objects, you absolutely can deal directly with the Managed Object Context, and I think this is arugably the preferred way. The NSArrayController is bound to the context, so you're just skipping the middle man. You might have a custom NSArrayController and want to use some logic from that, but that code can be anywhere.

There's nothing necessarily wrong with adding objects in code by talking to the NSArrayController, but it can be a lot slower in some cases (probably not in your example, though), and some of the methods don't take effect until the end of the current user event.

In my opinion, NSArrayController and NSTreeController should mainly be used when you need to connect user interface items to something.




 

Comments Temporarily Disabled

I had to temporarily disable comments due to spam. I'll re-enable them soon.





Copyright © Scott Stevenson 2004-2015