Core Data object gets invalidated - iphone

I have a problem with some Core Data objects that are somehow invalidated.
The managed object context is in the app delegate and I use it in a view table to fetch 'notes' objects from a database and display them.
I build an array for the sections (today, yesterday, etc.) and for each section an array with the notes in the section like this:
// in the .h file
NSMutableArray* data; // An array containing an array of thoughts for each section.
#property (nonatomic, retain, readonly) NSManagedObjectContext* objectContext;
// in the .m file, when loading the view
ThoughtsAppDelegate* appDelegate = (ThoughtsAppDelegate*)[[UIApplication sharedApplication] delegate];
objectContext = appDelegate.managedObjectContext;
NSEntityDescription* descriptor = [[NSEntityDescription entityForName:#"Note"
inManagedObjectContext:objectContext] autorelease];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:descriptor];
NSError* error;
NSArray* notes = [objectContext executeFetchRequest:request error:&error];
// example for one section
data = [[NSMutableArray alloc] init];
NSMutableArray* ccurrentSection = [[NSMutableArray alloc] init];
[data addObject:currentSection];
for(Note* t in notes)
[currentSection addObject:t];
When the view loads the first 5 notes are displayed (the rest don't fit in the view) and all is OK. But when I scroll down to view the next notes I get an
NSObjectInaccessibleException The NSManagedObject with ID... has been invalidated.
This happens for all objects in the array.
How is this possible? I checked and don't reset/release the context. Or is it bad to store a Core Data object and refer to it later?
Edit: this seems to happen also if I don't scroll and want to display details about a note when it's selected. Seems that as soon the first notes are displayed they're invalidated.

It would appear to be something with the way you manage the notes objects, but the code that is doing this is not in your example. The notes array is an autorelease array so unless you are retaining it somewhere it may be releasing before your load the next section from it.

Related

How to release an NSMutableArray?

I have an NSMutableArray with the following property:
#property (nonatomic, strong) NSMutableArray *alarmTableArray;
alarmTableArray = [[NSMutableArray alloc]init];
FMDBDatabaseAccess *db = [[FMDBDatabaseAccess alloc] init];
alarmTableArray = [db getAlarm];
I tried releasing this array but I end up with EXC_BAD_ACCESS.
I am really worried about this.
How to release this array?
You're using the descriptor of "strong" which is an ARC term. This should be retain and if you just set the property to nil it will release it automatically. You should set it to nil in your viewDidUnload since your ViewWillDissappear only means your viewcontroller is leaving visibility and not that it is being destroyed.
Updated Answer
I think I know what you're trying to do. You want grab an array of rows from your SQL and store it in one of your array.
One of the techniques for getting rows of data from SQL and storing into a class instance variable array is to NOT return a temporary array but pass the class instance variable array as a reference into your method and modify the array directly.
So instead of this pseudo-code
-(NSMutableArray *)doSomething
{
NSMutableArray *tempArray;
while (DB select statement has found rows)
{
CockTail *objCT = [[CockTail alloc] init];
objCT.name = #"...";
objCT.price = #"...";
[tempArray addObject:objCT];
[objCT release];
}
return [tempArray autorelease];
}
// class instance variable array
instanceVarArray = [[NSMutableArray alloc] init];
instanceVarArray = [self doSomething]; // here is where you confusion arise
You can do it this way:
-(void)doSomething:(NSMutableArray *)paramArray
{
// remove previously fetched data
[paramArray removeAllObjects];
SQL select statement
while(has rows)
{
CockTail *objCT = [[CockTail alloc] init];
objCT.name = #"...";
objCT.price = #"...";
// NOTE: we are directly modifying our class instance variable array
// here since it was passed by reference :D
// and so there is no need to worry about releasing the array
[paramArray addObject:objCT];
[objCT release];
}
}
// Now all you do is pass in your class instance variable array
instanceVarArray = [[NSMutableArray alloc] init];
[self doSomething:instanceVarArray];
Original Answer
Um, maybe I am wrong but aren't you essentially throwing away that "alloc init" on the first line here when you assign the array something from your FMDBDatabaseAccess:
// LINE 1: this instance of NSMutableArray here is allocated
alarmTableArray = [[NSMutableArray alloc]init];
// LINE 2
FMDBDatabaseAccess *db = [[FMDBDatabaseAccess alloc] init];
// LINE 3:this line here essential breaks the pointer link point to the NSMutableArray instance on line 1
alarmTableArray = [db getAlarm];
Now unless you do
// LINE 4
[alarmTableArray retain];
Otherwise, your alarmTableArray was never allocated (since you overwrote the pointer link). And as a result, you've caused a memory leak as your profiler told you.
Doing a release now would give your that EXEC_BAD_ACCESS
What I think you want to do is this:
alarmTableArray = [[NSMutableArray alloc]init];
FMDBDatabaseAccess *db = [[FMDBDatabaseAccess alloc] init];
// this now uses the setter method (mutator method generated by #property) to do the copy
self.alarmTableArray = [db getAlarm];
Looking at your while loop, I have to ask why are you releasing a local scope variable?
CockTail *cocktailValues = [[CockTail alloc] init];
...
[cocktails addObject:cocktailValues];
[cocktailValues release];
Breakdown of each line of code above:
When you alloc and init the CockTail object the release/retain count is 0.
Adding the object to the NSMutableArray increases the release/retain count to 1.
Releasing the CockTail object after you added it to array reduce the release/retain count back down to 0.
Therefore, later when you release the NSMutableArray or try to access an object in it, the objects are already gone.
Remember the number one rule, only release what you retain.

How to reflect the added entries on my UITableView?

I am using core data along with storyboards. I have 1 UITableView and 1 UIViewController. When I add entires to my entity it gets added in my context(database) but is not reflected in the table view when I go back. I have to run my app again and hen I can see my added entry. This is how I add new objects:
- (void)viewDidLoad
{
[super viewDidLoad];
myAppDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = myAppDelegate.sharedManagedObjectContext;
NSFetchRequest *personListRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *personEntity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:managedObjectContext];
[personListRequest setEntity:personEntity];
NSError *personListRequestError;
NSArray *personsList = [self.managedObjectContext executeFetchRequest:personListRequest error:&personListRequestError];
for(Person *thisPerson in personsList ) {
NSLog(#"First name is %#", thisPerson.firstName);
NSLog(#"First name is %#", thisPerson.lastName);
}
NSLog(#"the contents of array are %#",personsList);
personArray = [[NSMutableArray alloc] initWithArray:personsList];
}
I want my entries to be updated in my table when I add them, I don't want to run my app again to see them.
You use a fetched results controller to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.
While table views can be used in several ways, this object is primarily intended to assist you with a master list view. UITableView expects its data source to provide cells as an array of sections made up of rows. You configure an instance of this class using a fetch request that specifies the entity, an array containing at least one sort ordering, and optionally a filter predicate. NSFetchedResultsController efficiently analyzes the result of the fetch request and computes all the information about sections in the result set, and for the index. for more details apple doc and other sample
you need to tell the tableview to reload it's data:
[tableView reloadData];

CoreData DetailTableView BAD_ACCESS Error

Maybe I'm not going about showing a detail for a selected row using CoreData, but I can't figure out why I'm getting a "BAD_ACCESS" error. I've googled around and can't find what I'm looking for.
Basically I use CoreData to populate the data for a Table View. It retrieves all of the title attributes for all of the entities. When the user clicks on a row, I have a Detail View that needs to show the description for that entity. I think I need to make a new NSManagedObjectContext and a new NSEntityDescription for a new NSFetchRequest in my DetailViewController and then use a NSPredicate to say "where title = [user selected title]". I get an error when I select a row. See code:
- (void)viewDidLoad
{
// Do any additional setup after loading the view from its nib.
// Get the objects from Core Data database
Caregiver_Activity_GuideAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Definition"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(title = %#)", self.title];
[request setPredicate:pred];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error!");
// Do whatever error handling is appropriate
}
for (NSManagedObject *oneObject in objects) {
[definitionDescriptionTextView setText:[oneObject valueForKey:#"desc"]];
}
[objects release];
[request release];
[super viewDidLoad];
}
I comment out that code and everything works. But when I try to debug with breakpoints, nothing catches. So I'm more confused.
I know CoreData is probably overkill for what I'm doing but this is a learning app for me.
EDIT: I didn't include that I'm using a sqlite database that is pre-populated with the entities.
You can also download my project on my github page.
Normally, with a Core Data backed Master-Detail interface, you don't fetch for the Detail view.
When you select a row in the Master tableview, you are selecting a particular managed object instance. You then pass that managed object instance to the detail view. There is no need to refetch the object that you selected in the tableview.
A good example of this would be the Contacts app. The Master tableview would be a list of Contact objects (displaying the name.) When you select a row, the Master tableview controller takes the specific Contact object associated with the selected row and then passes it to the Detail view controller which then populates the Detail view using data taking from the properties of the passed Contact object.
So, that entire code block where the error occurs is unnecessary.
However, the immediate error in this code is that you are releasing an object you didn't create. In this line:
NSArray *objects = [context executeFetchRequest:request error:&error];
... you are not creating a NSArray instance with a init, new or create method. Instead, you are merely receiving an autoreleased NSArray instance created and returned by the context NSManagedObjectContext instance. When you release an object you did not create here:
[objects release];
... you cause the crash.
Conversely, you do create a NSFetchRequest here:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
... because you used init so you do have to balance that with:
[request relwase];
BTW, this type of code should not be put in viewDidLoad as the method is only called when the view is read in the first time from the nib file on disk. That is only guaranteed to happen once as the view may remain in memory when the user switches to another view. Instead, put code that needs to run each time the view appears in viewWillAppear.

iPhone - accessing an NSArray in a ViewController from the AppDelegate?

I want to be able to access an array of objects in my iPhone application. The array of objects is populated in the appDelegate of my application and I want to be able to access the array in one of my View Controllers.
I currently set up the array in my appDelegate.h file as follows:
NSArray *listObjArray;
#property (nonatomic, retain) NSArray *listObjArray;
I then populate it with some Strings like this in the AppDelegate:
listObjArray = [NSArray arrayWithObjects:#"Hello", #"How", #"are", nil];
NSLog(#"Array size = %i", [listObjArray count]);
It is synthesized and also released in dealloc. The NSLog returns the correct count here.
In my ViewController class I import the appDelegate like this:
#import "MyaAppDelegate.h"
I then access my appDelegate and the NSArray like this and try to Log the count in my View Controller:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication]
delegate];
NSLog(#"Before array set");
NSArray *newArray = [appDelegate listObjArray];
NSLog(#"After array set");
NSLog(#"array count = %i", [newArray count]);
NSLog(#"After array count");
The logging here gets to "After array set" and then I get "EXC_BAD_ACCESS" on the line where I try to print the count from the array in the View Controller.
The printing of the count works fine from the appDelegate and setting the newArray as the array from the delegate appears to work yet I cant do anything with it then.
Can anyone see what I am doing wrong?
I think your array declaration should be:
NSArray *newArray = [[NSArray alloc] initWithArray:appDelegate.listObjArray]
Be sure to release it after you are done! Though I'm not sure why you want to declare the new array, you could just do:
NSLog(#"array count = %i", [appDelegate.lstObjArray count]);
Hope this helps!
-Karoly
You have a memory issue: listObjArray = [NSArray arrayWithObjects:#"Hello", #"How", #"are", nil]; sets the instance variable directly. Shortly after this line the array gets released again, which results in you accessing a bad memory location in NSArray *newArray = [appDelegate listObjArray];, since it has been freed.
Use self.listObjArray = ... instead when populating the array. This will properly retain the object for you.
Please use the getter if it is sythesized. Since you are not using the getter, it is giving you bad memory access.
Also you should use retain or copy if you want to retain it or copy it. Else both newArray and listObjectArray will point to same memory location causing bad behavior.
NSArray *newArray = [[appDelegate getListObjArray] retain];
Try this
self.listObjArray = [NSArray arrayWithObjects:#"Hello", #"How", #"are", nil];
Allocation should be made on the getter.

Core Data Managed Object Context Saving Problem

I am trying to save an array into core data using NSData but my ManagedObjectContext says there are 0 objects and when I call it, I have it appearing as NULL. I have an entity called Event and 3 attributes in it (chatArray,...,...). I have tried for 11 hours and can't figure it out. I believe I am setting it wrong because the NSData is correct. How should I be setting this???
UPDATE
I am developing a chat application and I have the chat messages in a table view (It's an array of data). I need to save all the chat history when you exit the app and reload it. I have the messages coming in as strings and add it to the array for the table. If I didn't do an array, and I added the messages as strings of text to core data how would I add them to the array for the table view when you reload the app?
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSManagedObject *managedObject;
//
NSArray *array = [[[NSArray alloc]initWithObjects:#"one",#"two",#"three", nil]autorelease];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSLog(#"data %#",data);
NSLog(#"Array %#",[NSKeyedUnarchiver unarchiveObjectWithData:data]);
[(Event*)managedObject setValue:data forKey:#"chatArray"];
if ([self managedObject])
{
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext]];
[(Event *)managedObject setChatArray:data]; }
else {
Event *event = [[[Event alloc] initInsertingIntoManagedObjectContext:managedObjectContext]autorelease];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext]];
[event setChatArray:data];
}
NSError *error;
[managedObjectContext save:&error];
NSArray *myArray = [NSKeyedUnarchiver unarchiveObjectWithData:[(Event*)managedObject valueForKey:#"chatArray"]];
NSLog(#"chatArray %#",myArray);
Brandon,
First, in Core Data, BLOBs should be stored in leaf nodes (i.e. an entity that just contains the BLOB and a back to one relationship. (This pattern/convention has emerged because it is almost trivial to get a retain cycle of large blobs when there are other relations in the entity.)
Second, why are you storing these strings as an array and not as an entity with a time stamp, etc.?A BLOB is not particularly more efficient than individual rows plus the system can both search the messages and more flexibly store the rows. SQLite handles strings specially.
Third, it appears that you are composing your class rather than inheriting from your model entity, why? This makes your code more complex.
Finally, It is really hard to tell what you are trying to do. Could you include your full .h file? And the full method declaration?
Andrew