Editing NSManagedObject in duplicate context to merge it later on - objective-c

When a user double taps a view in my application a uipopovercontroller presents him with the fields which he can edit. (Much like in the iPad calendar app)
The view represents a NSmanagedobject. To be able to cancel the operations done in the uipopovercontroller my idea was as follows:
1) create a "editManagedObjectContext" in my viewcontroller for the popover and give it the persistentstorecoordinator of my main context used throughout my app.
editContext = [[NSManagedObjectContext alloc] init];
[editContext setPersistentStoreCoordinator:[myContext persistentStoreCoordinator]];
2) fetch the object represented on the tapped view (Task*) from the new "editContext"
task = [editContext objectWithID:[taskOrNilForNewTask objectID]];
3) Use this task to do all the editing and when the user finishes he can either:
Cancel the entire editing operation. This would just discard of the editContext and return.
Save. This would than merge the editcontext with the original context through mergeChangesFromContextDidSaveNotification :
Thus commiting the changes to the corresponding task in the original context.
Problem is task = [editContext objectWithID:[taskOrNilForNewTask objectID]];
results in a faulted object. And later on when I try to access the properties of a task object I get either the BAD_EXC error or my task object seems to be of some strange type ranging from: CALayer, NSCFData,...
My thought was that I might have to first save the original context, but that results in about the same errors. But since I saved just before I made the editContext I thought the save operation could be done in another thread and that could be a reason?
I just can't get my head around what I'm doing wrong and hope you guys can come up with some advice.
My approach was based on the approach in the CoreDataBook codesample from Apple (rootviewcontroller.m - (IBAction)addBook:)

Your problem was that objectWithID: returns an autoreleased object, which you were then storing in an ivar without retaining it. The system later deallocated it, and either you wound up with a garbage that gives you EXC_BAD_ACCESS or you wound up coincidentally with a different object at the same memory location. The errors you described made this clear.
The reason self.task fixes it is because the property self.task is declared retain, so assigning through the property automatically does the necessary retain. Do note that if you are not releasing it in dealloc then you will be leaking memory.

Related

How to save temp. changes of an object?

I need some advice how to handle the following case. I'm saving an object graph within core data. For simplicity lets say i have a User object (name, age, adress). Adress is another object with some properties. Now the User can change his Adress. If he changes it i need to remember these changes for the next order. After that i need to revert back to the original Adress. The User can also revert back at any time. Where should i save these temporary changes? I thought about adding a new entity like ChangedData but this somehow does not feel right. Basicly i need to remember the original object and if the User changes it i need to remember those for some Time as well. I hope i could express my problem well enough.
you need to use an NSUndoManager paired with your managed object context. Just add the following code to
- (NSManagedObjectContext *) managedObjectContext
in your app delegate implementation:
NSUndoManager *undoManager = [[NSUndoManager alloc] init];
[managedObjectContext setUndoManager:undoManager];
then use the methods of the undo manager to undo and redo your saves of the context.

Accessing a NSManagedObject causes EXC_BAD_ACCESS

Update: Tidied up question and made it a bit clearer
I am getting EXC_BAD_ACCESS crashes on a NSManagedObject.
I have a Sentence managed object that I pass to a modal view (addStoryItem) like so:
addStoryItem.sentence = (Sentence*)[fetchedResultsController objectAtIndexPath:indexPath];
AddStoryItem is set to retain Sentence:
#property (retain) Sentence *sentence;
Sometimes the user needs to do something that shows another modal (on top of addStoryItem) - which doesn't affect this object, but it does take a copy of a NSMutableSet - sentence.audiosets
If I they do view this modal I get an EXC_BAD_ACCESS whenever I try to access or set the sentence object or its properties, once the user is returned to addStoryItem
There is a current managed object context & fetched results controller
everything works fine unless I show that modal view controller (which, afaik, doesn't have anything to do with the sentence object)
Zombies is on, but it doesn't tell me anything (BRAINS?)
Here's a simple summary of what goes on:
user selects row in tableview
I get object from table and set the modal's sentence property then display the modal with the fetchedResultsController
I display a string, image and set a nsset from the sentence to ui aspects of the modal
if the user needs to modify the nsset they display another modal, with a copy of the first nsset (which doesn't change or access the sentence object)
if I try to set a property in the sentence after closing the 2nd modal (or NSLOG sentence) - EXC_BAD_ACCESS.
As far as I'm concerned I own sentence. Other properties of addStoryItem are still hanging around in memory - but sentence isn't there when I try to get to it. Yes, I release sentence in addStoryItem's dealloc - but that's not being called (I have a log statement in there).
Can you help? Happy to provide more code or info. Pretty frustrated!
You are creating a new sentenceToUpDate in your didSelectRowAtIndexPath:. Surely, this reference will be forgotten as soon as you are out of that method.
Rather, you should assign the retrieved object to your retained property, like this:
self.sentence = [fetchedResultsController objectAtIndexPath:indexPath];
Now the instance should be retained as expected.
Another possible culprit is your copy of the NSSet. Try creating a new NSSet to make sure you are not effecting the entity:
NSSet *setToBePassedToModal = [[NSSet alloc]
initWithSet:entity.toManyRelationship];

Prevent instance variables from being cleared on memory warning

I've got a relatively simple problem that's been evading solution for some time. I have a view controller and an associated XIB. The view controller is called FooterViewController. FooterViewController's view is set as the footer view of a tableview.
FooterViewController's view includes a label for showing feedback to the user. I would like this label to persist until its value is changed by my application. Under normal circumstances, it does. However, I've just begun testing with memory warnings, and I've found that after the view is unloaded in response to a memory warning, the label is cleared.
Here's what I've tried so far to solve the problem: in FooterViewController's viewWillUnload method, I store the label's text in an instance variable called statusString:
- (void)viewWillUnload
{
statusString = [statusLabel text];
testInt = 5;
NSLog(#"View will unload; status string = %#; testInt = %d",
statusString, testInt);
[super viewWillUnload];
}
Note that I've also set another instance variable, declared as NSInteger testInt, to 5.
Then, in FooterViewController's viewDidLoad method, I try to set the label's text to statusString:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Just before setting label, statusString: %#; testInt: %d",
statusString, testInt);
[statusLabel setText:statusString];
NSLog(#"View did load.");
}
However, this does not work. Further, in the log after simulating a memory warning, I see:
View will unload; status string = Invalid IP address Error code: 113; testInt = 5
(Note that "Invalid IP address Error code: 113" is the correct value for statusString)
Then, after navigating to FooterViewController again, I see:
Just before setting label, statusString: (null); testInt: 0
This indicates to me that for some reason, the instance variables of FooterViewController are being reinitialized when the view loads again. A final note: the method initWithNibName:bundle: is being called each time the view must reload, though I expect this; after all, the view must be reloaded from the NIB.
So, my questions are these:
Why do these instance variables appear to be nullified or zeroed in the process of unloading and reloading the view?
If I'm doing something incorrectly that's causing this nullification, what is it?
If I'm not doing anything incorrectly, and this is normal behavior, how should I handle maintaining state between loads of the view?
Thanks,
Riley
The statusString looks like a weak reference, not a strong property. It can not retain the labels's text, which gets deallocated with the label when the view is unloaded. That's why you get first a correct value (before the label is deallocated), and null later (after the label has been deallocated, and the weak ref nullified). Turn your statusString into a strong property, and that ARC magic won't bite you any longer.
It looks like you need to be using didRecieveMemoryWarning instead of viewDidUnload, since viewDidUnload is not guaranteed to be called in the event of a memory warning. If the crash is exiting the app completely then you need to be writing the data to disk using something like coreData. Save your data here and then call the super so the view will still be released. Hope that helps.
I figured out what was going on, finally. The issue was that I called the allocation and initialization methods for FooterViewController in its parent view controller's viewDidLoad method. When the views were dumped and subsequently reloaded, my view controller was re-initialized! This destroyed the original FooterViewController, which maintained the instance variables I needed, and replaced it with a brand-new VC.
The solution was to move [[FooterViewController alloc] init] to the init method of FooterViewController's parent VC, so that the initialization was only performed once per run cycle.
I've learned my lesson: don't reinitialize your view controllers unless you really mean to do so. As such, be very careful where you put your calls to the initializer in parent view controllers.
Thanks for the help I got from the two answerers.

Protecting my code from zombies from completion blocks

I'm familiar with the delegate pattern and nilling my delegates, especially when doing asynchronous calls which are still in progress when my view controllers disappear. I nil the delegate, and the callback successfully returns on a nil object.
I'm now experimenting with using completion blocks to make my code a little easier to read.
I call a network service from my view controller, and pass a block which updates my UITableView. Under normal circumstances it works fine. However, if I leave the view before it completes, the completion handler block is executed - but the UITableView is now a zombie.
Whats the usual pattern for handling this?
UPDATE WITH CODE SAMPLE
This is an iPad app, I have two view controllers on screen at once, like a split view. One is the detail, and the other is a grid of images. I click an image and it tell the detail to load the info. However, if i click the images too fast before they have chance to do the network call - I have the problems. On changing images the code below is called which counts the favourites of a image....
So here is my dilemma, if I use the code below - it works fine but it leaks in instruments if you switch images before the network responds.
If I remove the __block and pass in self, then it crashes with zombies.
I can't win... I'm sure i'm missing something fundamental about using blocks.
__block UITableView *theTable = [self.table retain];
__block IndexedDictionary *tableData = [self.descriptionKeyValues retain];
FavouritesController *favourites = [Container controllerWithClass:FavouritesController.class];
[favourites countFavouritesForPhoto:self.photo
completion:^(int favesCount) {
[tableData insertObject:[NSString stringWithFormat:#"%i", favesCount]
forKey:#"Favourites:" atIndex:1];
[theTable reloadData];
[tableData release];
[theTable release];
}];
Any tips? Thanks
SECOND UPDATE
I changed the way I loaded the favourites. Instead of the favourites being a singleton, I create an instance on each photo change. By replacing this and killing the old one - the block has nowhere to callback (i guess it doesn't even exist) and my code now just looks like the below, and it appear to be working:
[self.favourites countFavouritesForPhoto:self.photo
completion:^(int favesCount) {
[self.descriptionKeyValues insertObject:[NSString stringWithFormat:#"%i", favesCount]
forKey:#"Favourites:" atIndex:1];
[self.table reloadData];
}];
It doesn't leak, and doesn't appear to be crashing either.
I recommend you test that the tableview is not nil at the start of the block. It sounds like the tableview is properly discarded when its parent view goes off-screen, so after that point, no tableview operations are valid.
Retaining the UITableView within the block is a bad idea, because datasource/tableview updates can result in implicit method calls and notifications that will not be relevant if the tableview is not on-screen.
Block will retain any object that it references, except for those annotated with __block. If you want not to execute completion blocks at all, just make some property like isCancelled and check whether it is YES before calling completion block.
So you have a background operation which has to call back another object after it finishes and the object can be destroyed in the meantime. The crashes you describe happen when you have non retained references. The problem as you see is that the referred object goes away and the pointer is invalid. Usually, what you do is unregister the delegate inside the dealloc method so that the background task continues, and whenever it is ready to communicate the results back it says "Shoot, my callback object is nil", and at least it doesn't crash.
Still, handling manually weak references is tedious and error prone. You can forget to nil a delegate inside a dealloc method and it may go without notice for months before you encounter a situation where the code crashes.
If you are targeting iOS 5.0 I would read up upon ARC and the weak references it provides. If you don't want to use ARC, or need to target pre 5.x devices, I would recommend using zeroing weak reference libraries like MAZeroingWeakRef which work also for 3.x devices.
With either ARC's weak references or MAZeroingWeakRef, you would implement the background task with one of these fancy weak reference objects pointing back to your table. Now if the pointed object goes away, the weak pointer will nil itself and your background task won't crash.

CoreData: Clear changes from NSManagedObjectContext

I'm instantiating a NSManagedObjectContext object at the Application delegate level and sharing it across all my UIViewControllers. Here's the code that I use to access it in one of my View Controllers:
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
modelObj = (Model *) [NSEntityDescription insertNewObjectForEntityForName:#"Model" inManagedObjectContext:[appDelegate managedObjectContext]];
Now in this screen, I have a UITableView with 9 rows & each cell has a UITextField. As the user inputs values into the textfields, I assign them into modelObj. Now, my user has an option to cancel out and discard all the changes or save them to disk. I have the save code working fine. But in the case when a user tries to discard the changes, I'm not sure what to do. There doesn't seem to be a [managedObjectContext discardChanges] method to throw them all away.
I can think of a couple of ways of solving this.
Create a new instance of NSManagedObjectContext for each controller instead of sharing one across the application.
Or, I could create a bunch of NSStrings in my code and save user values in them and call insertNewObjectForEntityForName: only if the user clicks save.
Which way is the right one? Or is there a way to make NSManagedObjectConext discard all the changes that were made to it?
Thanks,
Teja.
NSManagedObjectContext has a simple method for this:
[managedObjectContext rollback];
This method "removes everything from the undo stack, discards all insertions and deletions, and restores updated objects to their last committed values." (documentation)
Unless I'm missing something, that should give you everything you need.
You might be looking for -refreshObject:mergeChanges: - the docs say that it resets an object from the persistent store, and if you pass NO as the second argument, you can choose not to reapply changes that have been made.
This will likely require you to store a set of objects that you have changed (for the first argument), then clear that set when you commit changes in your context to the store. This should be a pretty trivial addition, though.
Swift 5
managedObjectContext.rollback()