Problem with NSManagedObjectContext crashing app - iphone

I have a navigation based app that uses Core Data for storage. The schema for this particular section is as follows:
Scene hasMany Tasks hasMany DeviceCommands
Tasks also have many other objects besides DeviceCommands (like DelayCommands etc), but this is beyond the scope of the problem (I think).
DeviceCommands also link to a parent object as follows:
Device hasMany DeviceCommands
Now, the user can create a new Task, which pops up a modal view to select the Device. When you select a device, a tableview is pushed with the DeviceCommands that belong to the Device. When you select a DeviceCommand, it assigns it to the Task with task.deviceCommand = device.deviceCommand. For some reason, and this only happens in this section of the app, the app crashes with the following output in the console:
2010-10-26 15:37:33.337 livOS4[47226:207] Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. * -[NSMutableArray objectAtIndex:]: index 35 beyond bounds [0 .. 0] with userInfo (null)
Index 35 refers to the index of the DeviceCommand in the UITableView and NSFetchedResultsController. Through a process of trial and error I've discovered that the NSMutableArray is the array with the Devices. If I add a new Device to the app, the array bounds are [0 .. 1].
I added an observer for NSManagedObjectContextObjectsDidChangeNotifications but it crashes before the observer receives anything.
Can anyone help or offer any advice for fixing this?

I have the same problems with NSFetchedResultsController.
Problem disappear then I add this to my viewController
- (void)dealloc
{
self.fetchedResultsController.delegate = nil;
self.fetchedResultsController = nil;
}

Fixed it this morning. Turned out to be a problem with how I was assigning the NSFetchedResultsController delegate.

Related

reload table view : indexPath isn't reset ios 6

I'm making an iOS 6 program which downloads JSON data from a website and displays it in a table view. I added a pull to refresh method witch works fine. I can go in the settings view controller (secondViewController) and change the address, everything works. Then, I use the pull to refresh method and my tableView is reloaded. But if I reload 3 times AFTER changing the address, my app crashes. I get this error :
*** Terminating app due to uncaught exception 'NSRangeException',
reason: '*** -[__NSArrayM objectAtIndex:]: index 10 beyond bounds for empty array'
*** First throw call stack:
(0x1ca1012 0x10dee7e 0x1c430b4 0x3084 0xdd8fb 0xdd9cf 0xc61bb 0xd6b4b 0x732dd 0x10f26b0 0x229dfc0 0x229233c 0x2292150 0x22100bc 0x2211227 0x22bb333 0x22bb75f 0x1c60376 0x1c5fe06 0x1c47a82 0x1c46f44 0x1c46e1b 0x1bfb7e3 0x1bfb668 0x22ffc 0x1fbd 0x1ee5)
libc++abi.dylib: terminate called throwing an exception
What am I doing wrong ? And how can I fix that problem ? Thanks for your help !
The key design consideration that leaps out is that your retreiveData method is clearly updating the model (the citiesArray) asynchronously, which means that any interaction with the tableview while this is taking place may fail. You should never asynchronously update the actual citiesArray itself. The update to that array should happen in the main queue.
You should change retrieveData to not touch the existing citiesArray, but rather create and return a new array, and then, in the code you dispatch back to the main queue, only then replace the existing citiesArray and call reloadData, something like:
- (void)refresh:(UIRefreshControl *)refreshControl {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *newCitiesArray = [self retreiveData];
dispatch_async(dispatch_get_main_queue(), ^{
if (newCitiesArray) {
// you presumably only want to reload the data if `retrieveData` was successful
citiesArray = newCitiesArray
[myTableView reloadData];
}
[refreshControl endRefreshing];
});
});
}
Clearly, this will involve some changes to retrieveData, too, but hopefully that's self explanatory. If not, update your question with that code, and we can make further suggestions. But we really shouldn't need to go there, as I suspect you understand the change that needs to take place there.
There are other, more subtle issues you might want to tackle, too, such as thinking about whether you really want to use a global queue, which is concurrent (e.g. if you hit refresh while the previous refresh going, do you really want two queries going on concurrently with your server ... because you dispatch the updates back to the main queue you won't crash, but it's inefficient, you're not guaranteed the order that they'll complete, etc.). You might also want to use NSOperationQueue in which you can write code to permit the cancellation of previous requests, etc.
But all of this is a little complicated and is of secondary concern to your main issue, the crashing. Refactoring the retrieveData code to ensure you don't touch citiesArray, itself, as outlined above, should address that.

App crashes when adding names to UIPickerView

When I click a button, a UIAlertView prompts the user to type a name. This name is then created as a new 'Customer' object and inserted into a mutable array.
There is a separate mutable array called 'CustListByName', which stores a list of all names.
The problem im having is that when adding a second or third name, the app crashes. Sometimes it happens on the second try, other times on the third try. There is no information given in the debugger except for (lldb). The program reports EXC_BAD_ACCESS and then it dumps me to a screen with a bunch of assembly code.
The crash is happening in these lines of code:
Essentially, it clears the array of names and then repopulates it based upon the object array. I've studied in step by step with a breakpoint but everything seems correct up until the point of crash. It is also confusing why this happens on the second or third try, never the first.
[custListByName removeAllObjects];
for (Customer *object in custListByObject) {
[custListByName addObject:object->name];
}
Here is the code where a customer is created and inserted everytime the new customer button is clicked:
Customer *tempCust = [[Customer alloc] init];
tempCust->name =[[alertView textFieldAtIndex:0] text];
[custListByObject addObject:tempCust];
[tempCust release];
I would really appreciate help with this, thanks!
What I suspect is happening is that the UIPickerView is attempting to load a row using information from your customer array after you have already cleared it, and before you repopulate it. This would cause a bad access error.
What you may consider doing instead, is keeping two arrays, an NSMutableArray for loading the customers, and an NSArray as the actual data source for the UIPickerView. Then right before you reload the UIPickerView, you say:
dataSourceArray = [loadingArray copy];
[pickView reloadAllComponents];
Hopefully this helps.
Edit:
Here's what your updated code would look like if your loading array was called loadingCustListByName:
[loadingCustListByName removeAllObjects];
for (Customer *object in custListByObject) {
[loadingCustListByName addObject:object->name];
}
custListByName = [loadingCustListByName copy];
[pickView reloadAllComponents];
Doing this will ensure that the UIPickerView's datasource array always matches up with the number of rows it thinks it has.

Core Data fault: how to load an entities relationship set of objects correctly

For example:
I have two entities named Projectand Todo where a project has many todos (to-many relationship) and each todo has one Project(see image).
In my ViewController.h I have something like:
...
NSArray *projectArray;
NSArray *todosArray;
#property (nonatomic,retain) NSArray *projectArray;
#property (nonatomic,retain) NSArray *todosArray;
...
In my ViewController.m I have something like:
...
#synthesize projectArray,todosArray;
...
self.projectArray = [self fetchRequestForAllProjects];
...
The user has an interface where he is able to select between all different projects. As soon as the user selects a project, the related todo objects have to be set to be loaded and presented.
Question 1: How do I load the set of todos into the todosArray in the best way?
I was doing it like that (also in the ViewController.m):
...
// after deselecting a project entry I reset the todosArray
self.todosArray = nil;
...
//when the user selects a new project I reset the todosArray like this:
self.todosArray = [selectedProject.todos allObjects];
...
But somehow the app very rarely crashes in the last line of code. Is there any problem with my code?
Question 2: Would it be better to use another fetch request for the todos Objects?
UPDATE:
I am using the todosArrayin various methods of the ViewController.m:
(a) get the count of objects,
(b) present each todos entry inside a table view row, and
(c) to identify the selected todo entry (threw row selection)
Answer #1
It is best to sort them when you pull everything out of the set into an array. This will keep your user experience consistent:
NSSet *projectTodoEntities = [mySelectedProject valueForKey:#"todos"];
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:#"myKey" ascending:YES];
NSArray *sortedToDos = [projectTodoEntities sortedArrayUsingDescriptors:[NSArray arrayWithObject:sorter]];
Answer #2
No, fetching is expensive compared to just accessing a relationship. A fetch will hit disk, etc.
For answer #1.
1). Please make sure whether selectedProject is deleted on other thread, if it is deleted, core data will mark this NSManagedObject as invalid, when you try to access property of this object, a NSObjectInaccessibleException will be thrown.
2). All NSManagedObject associates NSManagedObjectContext, the context is limited on certain thread or thread queue, when you access "todos" relationship while it is in fault state, it will trigger a fetching from persistent store, you must make sure whether execution thread is valid for NSManagedObjectContext, otherwise you should use below code.
NSManagedObjectContext *context = [selectedProject managedObjectContext];
__weak YouControllerClass *weakSelf;
[context performBlockAndWait:^{
weakSelf.todosArray = [selectedProject.todos allObjects];
}];
Answer #2: Would it be better to use an other fetch request for the todos Objects?
By default the "todos" relationship is returned as fault state, when you access project property "todos:, it actually triggers fetching from persistent store for 1st time, core data may cache these "todos" objects in memory later, so you will get fast access in future (unless you reset NSManagedObjectContext)
For most scenarios, like user checks his limit todo lists, it is ok to trigger another fetch request, the performance is not real problem if there is no huge blob data in todo object.
For performance critical scenarios, like use core data to save hundreds of photos and metadata as a relationship, when you draws all these photos on UIView based on height, width or URL property of photo object, you make consider pre-fetching photo meta to avoid performance hit (io operation).

core data exc_bad_access when setting relationships

I have a problem with core data when setting relationships:
The code below crashes randomly when setting the relationships between country and region.
If I disable the second for-loop, the method completes without errors.
Everything happens within the context living on the background-thread.
Again: I can create the objects for the regions and countries without trouble and they show up in the Simulators database just fine - but as soon as I try to set the relationships between then, the app crashes randomly.
Any thoughts ?
for (Region* region in regions) {
// only store if region code isn't empty
if (region.m_RegionCode != nil && [region.m_RegionCode length] > 0) {
NSManagedObject* cdRegion = [NSEntityDescription insertNewObjectForEntityForName:CDREGION inManagedObjectContext:self.objectContextBackground];
[cdRegion setValue:region.m_RegionCode forKey:#"code"];
[cdRegion setValue:region.m_regioncodedescription forKey:#"name"];
}
}
[self saveBackgroundContext];
for (Region* region in regions) {
if (region.m_RegionCode != nil && [region.m_RegionCode length] > 0) {
NSManagedObject* cdRegion = [self getManagedObject:CDREGION withCode:region.m_RegionCode];
NSManagedObject* CDCountry = [self getManagedObject:CDCOUNTRY withCode:region.m_countrycode];
[cdRegion setValue:CDCountry forKey:#"country"];
}
}
well - just to let you know: it actually was the problem (I know - it is listed on top of all pages regarding this topic :-) that I used a context between threads.
I mixed up queues and threads. I created a single background-queue where I used my "background-context" ... but of course i created several threads within that, who where interacting with the context... so...
btw: it was this excellent article that finally clarified it for me:
(came right in time :-)
http://www.cimgf.com/2011/08/22/importing-and-displaying-large-data-sets-in-core-data/
This error message is likely related to memory problem, about using (or releasing) already deallocated object.
You should run profiler to spot the memory problem, or debug with a breakpoint after the save method and following a line by line execution until it crashes.
Just guessing, if you are creating or getting the Regions object with some sort of factory method, probably it gets deallocated in the middle of execution by that method, sometimes the routine is fast enough to complete before the dealloc, sometimes not, that could explain the randomness.
Try to retain the Regions at the beginning and release at the end of the second loop.

Changing Core Data Items crashes App

I am running into another problem with my Iphone App which I just can't solve myself.
I implemented a kind of organizer functionality in my latest app.
There one can create appointments which are displayed in a tableview and persisted in a CoreDataStore. I use 4 classes:
an overview where appointments are displayed
a view with textfields to put in Values for place and name of appointment (create/edit view)
a view with DatePicker to define start- and enddate
a controller which handles creation and deletion of items using this methods:
The code:
-(void)createAppointmentObjectWithDate:(NSDate *)
appointmentDate name:(NSString *)appointmentName
description:(NSString *)appointmentDescription
eDate:(NSDate *)appointmentEndDate
{
NSManagedObjectContext *managedObjectContext = [[CoreDataManager sharedManager] managedObjectContext];
AppointmentObject *newAppointmentObject = [NSEntityDescription insertNewObjectForEntityForName:AppointmentObjectEntityName
inManagedObjectContext:managedObjectContext];
newAppointmentObject.appointmentName = appointmentName;
newAppointmentObject.appointmentDescription = appointmentDescription;
newAppointmentObject.appointmentDate = [appointmentDate earlierDate:appointmentEndDate];
newAppointmentObject.appointmentEndDate = [appointmentEndDate laterDate:appointmentDate];
}
-(void)deleteAppointmentObject:(AppointmentObject *)appointmentObject triggeredByUser:(BOOL)byUser{
NSManagedObjectContext *managedObjectContext = [[CoreDataManager sharedManager] managedObjectContext];
[managedObjectContext deleteObject:appointmentObject];
}
But all kind of crazy stuff is happening which makes my app crash with "SICBART" message:
2010-10-13 17:35:04.630 didacta[109:307] Serious application error. Exception was caught during Core Data change processing.
This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.
-[CALayer controllerWillChangeContent:]: unrecognized selector sent to instance 0x19f150 with userInfo (null)
2010-10-13 17:35:05.118 didacta[109:307] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CALayer controllerWillChangeContent:]: unrecognized selector sent to instance 0x19f150'
errors appear while doing this:
creating new Appointment and pressing "Done" (should trigger creation and pop overview)
changing appointments and pressing "Done" (should send changes and pop overview)
tapping on an appointment in overview ( should pop create/edit view and hand over values)
deleting an item
sometimes I can even delete an appointment but then the order of the items in the tableview is somehow gotten twisted so the index of the tableview isn't pointing to the index of the appointment anymore.
Right.
-[CALayer controllerWillChangeContent:]: unrecognized selector sent to instance 0x19f150 with userInfo (null)
That's your error. You have an NSFetchedResultsController whose delegate is a CALayer. This sounds like the original delegate was deallocated and a CALayer was allocated using the same region of memory. The fix is to find the offending -dealloc and add something like self.myFetchedResultsController.delegate = nil; self.myFetchedResultsController = nil; assuming you're using properties.
You can sometimes help debugging things like this by enabling zombies (go to Project → Edit Current Executable or so, select Environment, add the NSZombieEnabled environment variable, and set its value to "YES" or so; uncheck the checkbox when you've finished debugging). Zombies cause an exception when a message is sent to a deallocated object. (Zombies are not deallocated by default, so your application will effectively leak; remember to uncheck the checkbox!).
"unrecognized selector" makes it sound like maybe your data model doesn't contain some of the Entity attributes that you're trying to use. For example, maybe you're trying to set an attribute of the object that doesn't exist.
Try creating a breakpoint at newAppointmentObject.appointmentName = appointmentName; and step through it to see at what point the error occurs.