Been browsing through the posts similar to this one but none answered my problem.
Like CoreDataBooks, I use a separate MOC for adding and editing items and observe the proper notifications, like so:
- (void)addControllerContextDidSave:(NSNotification*)saveNotification {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
NSLog(#"merging changes in rootview");
}
When I do a save, it's observed correctly. Debugging the notification shows the right context, and debugging the context shows the just added item. The log message also prints just fine. But nothing happens. None of the delegate methods of the NSFetchedResultsController fire, nor is the table view updated.
I tried no cache or clearing it. I've tried reloadData on the tableView, but nothing.
When I quit the app and restart, it shows up fine.
UPDATE:
The notification does indeed contain the right context as its object, which contains the correct item, but the userInfo hash has nothing in it:
userInfo = {
inserted = "{(\n)}";
updated = "{(\n)}";
}
So it looks like the notification doesn't have any changes in it, despite having the correct context with the changed item.... hmmmm.
And the award for dumbest mistake goes toooooo ... Christoph!!
The reason the notification was not showing anything was that I had forgotten to take out a save on the separate MOC before calling the delegate that set up the notification and then saved again.
Since the MOC was just saved, the second save had no changes and thus the notification stayed empty. Removing the first save, as I should have done much earlier, did fix the problem.
Related
iPhone forms, where do you put your validation?
People suggested me to directly put the value of my textfields in my core data entity, which I did (see my second edit). Thing is, when a person update an entity, since I put directly the value that the user is typing in my entity, even when I don't save my ManagedObjectContext, my TableViewController (using NSFRC) updates with the latest infos the user entered (even if he pressed cancel). If I restart the app, since it didnt save, everything is back to normal.
How can I avoid this?
I'm assuming you are using the same NSManagedObjectContext for both viewControllers. You could use a separate NSManagedObjectContext for each viewController. If you are using iOS 5.1 set the 1st viewController's managedObjectContext (MOC) as the parent of the 2nd viewControllers managedObjectContext. So if you save the 2nd view's MOC via [theContext save], it will merge the changes with the 1st MOC automagically. If you don't want to keep the changes simply pop that view off the navigation stack.
MOC's are scratchpads. So in essence you want to use that 2nd viewController as a scratch pad, until you hit save.
I implemented the LazyTableImages project (link) by Apple, but in my version I used RestKit to obtain the data and my UItableviewcontroller was push onto navigation stack.
So I eschew whatever apple does in the app delegate to get the xml. I dont think that's the problem. My problem is that when you back out of the UITableviewcontroller using either the nav back button or accessing another tabbar item and coming back, the images that were loaded there previously show up, but immediately it loads the placeholder image. Basically, the opposite happens.
It's like the UITableview cached data, so when you come back it interferes with the Lazy Table Images. I need to know has anyone implemented this code where they had to back out?
EDIT:
Looks like imageDownloader is not nil the second time, which prevents the image from loading. I'm still figuring out how to bypass it. Of course, I can just take out the condition, but I dont know if that is "bad" for performance.
imageDownloadsInProgress, a mutable dictionary, still has all of its data even if you back out. It has become a different question now, how do I delete imageDownloadsInProgress if a user hits back or strays from the current view.
imageDownloadsInProgress is retained, but I added [imagesDownloadsInProgress release] in the dealloc method, however I don't think that runs.
-(void)startEventImageDownload:(WhatsonEvent *)eventRecord forIndexPath:(NSIndexPath *)indexPath
{
EventImageDownloader *imageDownloader = [imageDownloadsInProgress objectForKey:indexPath];
if(imageDownloader == nil)
{
NSLog(#"%#",eventRecord.title);
imageDownloader = [[EventImageDownloader alloc] init];
imageDownloader.eventRecord = eventRecord;
imageDownloader.indexPathInTableView = indexPath;
imageDownloader.delegate = self;
[imageDownloadsInProgress setObject:imageDownloader forKey:indexPath];
[imageDownloader startDownload];
[imageDownloader release];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
}
The way I do it is to build my own cache and save the images in the user documents directory. When I call [tableView reloadData] (you are calling that, right?) it first checks for each cell if the image is there locally, otherwise it will lazily load them from the feed. Tell me if you need code for this.
The problem was that the self.imageDownloadsInProgess = [NSMutableDictionary dictionary] was placed in the ViewDidLoad method with the intention of resetting the dictionary every time. However, if you place the code within a view pushed onto a navigation controller, the ViewDidLoad only executes the first time (I'm not positive that is the case). I added the line to ViewWillAppear since it runs every time the view is placed on screen.
I have created a window based application with the following
a TableViewController (Without a XIB file)
a ViewController (With a XIB file) <-- to be used as modal view
a CoreData model to store some data
I managed to load the application and populate the TableView with the data from the Entity, and I was able to scroll through all of the cells of the TableView, without any issues.
I added a UIBarButton item (rightBarButton) that causes a Modal View to appear for the user to input some data. The model view has a SAVE and CANCEL buttons.
The problem is once I press the Cancel Button, I go back to the TableView but if I try to scroll throgh the items in the tableview, the app crashes.
After 4 hours of searching Google and StackOverflow, I was not able to see why my app crash. I did however notice by the debugger that the ManagedObjectContext is set to NIL the second time I scroll the tableview (after the modalview is dismissed), although no data is changed and no insertion/deletion occured.
I tried using a timer to call reloadData as I found some answers on StackOverflow, but that did not work. I tried setting the ManagedObjectContext as a property with retain and removed all occurences of [myManagedObjectContext release] to avoid releasing it earlier than needed, but that did not help.
It seems that I am doing an obvious mistake, but I am not sure where.
Please help.
ivars do not become nil just because they're released somewhere else (at least not in iOS 4.3). So an over-release is not the specific cause of myManagedObjectContext becoming nil. Assuming you're using accessors to reference your ivars (and you should be), hand-implement setManagedObjectContext: and put a breakpoint in there to see who's calling it. Alternately, you can add a gdb watchpoint to myManagedObjectContext to see when the memory is changed.
You haven't indicated what the crash stack is when you crash. You should be focused on what memory you're accessing at the point of the crash, and ensuring that the crash is due to a memory violation rather than an exception. Check your debugger output. Often it will tell you what's happening.
[EDIT: simplified version of the question]
mainMOC is the primary managed object context
editorMOC is a managed object context created in editorViewController with an undo manager so the user can edit a single managed object
after editorMOC saves, mainMOC refreshes the updated managed object in the notification handler for NSManagedObjectContextDidSaveNotification
In the save handler, if I use [mainMOC refreshObject:obj mergeChanges:YES] the updates to the object are not reflected in mainMOC post-refresh. If I use [mainMOC refreshObject:obj mergeChanges:NO] the object is invalidated and at the next fault the changes are reflected in the data loaded from the store.
QUESTION: Why would the object not reflect the update when mergeChanges:YES is specified?
[ORIGINAL QUESTION]
I have a core data based app with multiple managed object contexts. The app is complicated and proprietary so I cannot simply share code directly from the app. I have created a simple test app in an attempt to reproduce my issue but the test app doesn't exhibit the problem. I have not been able to find a logical difference between the implementations. I apologize for not posting sample code, I believe I explained the implementation well below. If something is not clear, please ask in the comments and I will do my best to clarify.
Here's my situation. Everything described below is running on the main thread.
The app has a primary managed object context called mainMOC that is accessed on the main thread and used with NSFetchedResultsControllers to display data in various table views.
I have a view controller called EditorViewController that allows editing of an existing object of a particular entity. This view controller creates it's own managed object context called editorMOC using the same persistent store coordinator with an undo manager so changes can be rolled back or saved when dismissing the editor.
EditorViewController is observing the NSManagedObjectContextDidSaveNotification. When this notification occurs, the notification handler calls [_mainMOC mergeChangesFromContextDidSaveNotification:notification] to merge the changes from editorMOC into mainMOC.
The table view controller that uses an NSFetchedResultsController is handling the controller delegate messages.
I have added NSLog output to look at look at what happens in all of the above steps and I have verified the following:
I can see that the object is modified and saved in the editor.
I can see that the NSManagedObjectContextDidSaveNotification is called and that the updated object is included.
I can see that the fetched results controller is receiving the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: protocol message.
I can see that mainMOC is not reflecting the updates and that the updated object has not been invalidated by mergeChangesFromContextDidSaveNotification:.
If I quit and relaunch the app, the updates were committed
For reference, both my main app and test app implement the above functionality but the test app shows the updates merged correctly and the main app does not.
I am looking for suggestions on what would cause mergeChangesFromContextDidSaveNotification: not to successfully merge and/or invalidate the updated object.
Thanks!
The fetched results controller does not receive the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: delegate message. Instead it sends it to the delegate in response to a change in the context. If that delegate message is being sent and does contain the proper object, then the main context is aware of the changes.
If so, then your error is most likely in the the code interfacing the fetched results controller and the tableview. Something is preventing the object from appearing correctly.
The issue I had that caused mergeChangesFromContextDidSaveNotification not to work was I created a new NSPersistentStoreCoordinator for each new NSManagedObjectContext. When I shared NSPersistentStoreCoordinator between all MOCs, mergeChangesFromContextDidSaveNotification works perfectly. Also, make sure mergeChangesFromContextDidSaveNotification is called on the thread that owns the MOC.
From the research I did, it is safe to share NSPersistentStoreCoordinator between threads for use with NSManagedObjectContext. NSManagedObjectContext will lock the persistent store as necessary.
One possible answer: you have a third MOC you didn't know was there / forgot about. (This happened to me.)
I had
mainMOC
editorMOC
viewerMOC which came about by accidentally misguided subclassing - it was supposed to be looking at the main MOC, but instead was creating its own and looking at a frozen state. The "checking for edits" relationship was going the other direction, because it was expected to be the "editor" in this scenario.
the notification is correctly invoking the callback, and the data is being merged correctly into the main MOC (which i could tell because the data was correct on relaunch)
note: refreshObject:mergeChanges: is not needed. it's something i tried, too, when it wasn't working, but the merge of the notification should take care of all the objects for you.
- (void)twinStackUpdated:(NSNotification *)notification {
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
not sure that this was your problem, but it might be someones, so there it is.
In an effort to accept something even though I don't have a definitive solution, here's what I've done to address this and the problem seems to be resolved.
I believe this was a cache issue with NSFetchedResultsController. I've since simplified this code and made sure that each NSFetchedResultsController uses it's own cache. I've been able to remove the call to 'refreshObject' and things seem to be working correctly.
Here's the issue – I followed along with the Apple lazy-load image sample code to handle my graphical tables. It works great. However, my lazy-load image tables are being stacked within a navigation controller so that you can move in and out of menus. While all this works, I'm getting a persistent crash when I move into a table then move immediately back out of it using the "back" button. This appears to be a result of the network connections loading content not being closed properly, or calling back to their released delegates. Now, I've tried working through this and carefully setting all delegates to nil and calling close on all open network connections before releasing a menu. However, I'm still getting the error. Also – short posting my entire application code into this post, I can't really post a specific code snippet that illustrates this situation.
I wonder if anyone has ideas for tasks that I may be missing? Do I need to do anything to close a network connection other than closing it and setting it to nil? Also, I'm new to debugging tools – can anyone suggest a good tool to use to watch network connections and see what's causing them to fail?
Thanks!
Have you run it through the debugger (Cmd-Y)? Does it stop at the place where the crash is happening? That should show you in code where the issue is happening. I'm betting the issue has to do with over-releasing something rather than cleaning up connections. Are you getting EXC_BAD_ACCESS? Check any delegates and make sure they are nil when -viewWillDisappear gets called. That way, if anything tries to call back to a delegate, it will just be a no-op.
You may also want to try enabling zombies (NSZombieEnabled) which will tell you when an object that has been released is being accessed again. It's very helpful in finding over-released objects.
Ah ha... after a large zombie hunt (thanks, Matt Long), I discovered that the issue stems from an error in Apple's LazyTableImages sample code. That example provides the following implementation for canceling all image loads, which I turned into a general-purpose stopAllImageLoads method...
From RootViewController.m in LazyTableImages sample code:
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// terminate all pending download connections
NSArray *allDownloads = [self.imageDownloadsInProgress allValues];
[allDownloads performSelector:#selector(cancelDownload)];
}
There is in error in the last line of the above method where performSelector is called on an array of objects. The above implementation calls the selector on the array itself, rather that on each object in the array. Therefore, that last line should be this:
[allDownloads makeObjectsPerformSelector:#selector(cancelDownload)];
Once that line was changed, everything else fell into place. It turns out I wasn't calling my stopAllImageLoads method where I meant to – I had disabled it at one point because it was causing an error. Once that was back in place, the memory issues cleared up because image loads were successfully canceled before the table delegate was released.
Thanks all for your help.
If you're doing ANY asynchronous function (network requests, Core Location updates, etc), you run the risk that your view controller that is the delegate of that action is deallocated by the time the async function returns. i.e. you back out of the view and take the delegate target away from the background process. I've dealt with this several times.
Here's what you do. Use the ASIHTTPRequest library (which you should be doing anyway--it's brilliant). Create a synthesized property to hold your request. Then in viewWillDisappear, call -cancel on your request. To be safe, I also set its delegate to nil, but that should be unnecessary.
Here's a sketch of what you want to do. Note I typed this right here, haven't syntax-checked it or anything.
#implementation MyViewController
#synthesize req //this is an ASIHTTPRequest *req.
-(void)viewDidLoad
{
//make an NSURL object called myURL
self.req = [ASIHTTPRequest requestWithURL:myURL];
self.req.delegate = self;
[self.req startAsynchronous];
}
-(void)viewWillDisappear
{
[self.req cancel];
}
-(void)requestFinished:(ASIHTTPRequest *)request
{
NSString *string = [request responseString];
}