Accessing a NSManagedObject causes EXC_BAD_ACCESS - objective-c

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];

Related

NSManagedObject without values

Im passing a NSManagedObject to a UIView. So Im showing a UITableView of meetings brought from CoreData, if you tap on one of the meetings you will be able to see, on another view, more info of that meeting, info that is contained in a NSManagedObject. I want to pass that NSManagedObject to the view that will show its info.
So I created a init method of that view like this:
-(id)initWithMeeting:(NSManagedObject *)aMeeting{
_theMeeting = aMeeting;
return self;
}
Then I use the info in the _theMeeting object to show it in the view that I just created in the ViewDidLoad. The problem is that whenever I try to access any of the values of the NSManagedObject it crashes, it has values in the init but not in the ViewDidLoad.
I believe it has something to do with the Managed Oriented Context, but the Managed Oriented Context never disspears, is an attribute of the AppDelegate.
So I dont know how to pass that Object and keep it.
I also declared theMeeting:
#property(nonatomic, copy)NSManagedObject *theMeeting;
Hope you can help me.
Are you using the accessor to assign theMeeting? I think you're just bypassing it so aMeeting is not retained or copied, and therefore the crash.

How do I prevent NSMutableArray from losing data?

The first view of my app is a UITableView.
The user will choose an option and the next view will be another UITableView. Now the user can tap on an "add" button to be taken to another UIViewController to enter text in a UITextField. That text will then appear in the previous UITableViewCell when saved.
The issue I am having: if I back out to the main view and then go back to where I previously was, that inputed text is now gone.
How can I make sure that text is not being released or disappears like this?
You might want to store this array somewhere else in your project, like in an MVC (data model). You could create a new class for it that passes the information through the classes and stores the array in one place. Then once you add to the array, you could reference that class and call a method in that class to store the text in the array and whenever you load the table view it loads with that array in the class.
In my case, I would do this, but I would make everything class methods (where you cannot access properties or ivars) and just store the array in the user defaults / web service or wherever you need and retrieve and add/return it like this:
+ (NSMutableArray *)arrayOfSavedData {
return [[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"];
}
+ (void)addStringToArray: (NSString *)stringFromTextField {
[[[[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"] mutableCopy] addObject: stringFromTextField];
}
The mutableCopy part is important because arrays don't stay mutable after you store them into the user defaults
The reason the text is gone, is probably because you're instantiating new controllers when you go back to where you were. You can keep a strong reference to your controllers, and only instantiate one if it doesn't exist yet. Exactly how to do this depends on how you're moving between controllers -- whether you're doing it in code, or using a storyboard for instance.
This kind of issue is very frequent. When you move around multiple controllers and views.
Each time you load a new view and controllers are alloc+init, new set of values are assigned and previous values are not used!!!.
You can use SharedInstance/SingletonClass so that it is allocated & assigned once and does not get re created with new set of values.

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.

Why is my outlet nil?

I'm having trouble with a cocoa project. I'm displaying a keyboard composed of NSButtons, and I'd like that when I click on one of the keys, the label is added to a NSTextField. I have a controller that I use as a singleton, so each key "knows" how to access the controller. In the controller, I have an outlet linked to the NSTextField. When I click on a key, nothing happens. So I used something like NSLog(#"%#", [[[OakController] sharedInstance] textarea]) on a mouseDown event, and in the console output, I get (null).
Long story short, my outlet is set to nil, and I don't know why it is that way, or how to solve that...
Here's the code of the controller : https://gist.github.com/1090564. Sorry for the lack of syntax coloring.
Thanks for reading guys!
My guess is that you actually have multiple instances of OakController instead of one like you expect. Did you drag a blue cube into your IB document and change its custom class to OakController? That will allocate and initialize a new object each time. I'd guess that your sharedInstance method also allocates and initializes an instance.
Try adding an awakeFromNib method to OakController, and add a break point. Log self's pointer value. In your second case were the outlet is unexpectedly nil, also log self's pointer address.

Editing NSManagedObject in duplicate context to merge it later on

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.