I have an iPhone/iPad app where one of the main functions is to arrange objects on a plot. I use Core Data to manage the objects' relationships; for this example, I'll talk about Units, which have a to-one relationship to a Plot (and a to-many inverse). Units' properties include positionX, positionY, and angle.
Each Unit (inherits from NSManagedObject) is paired with a UnitViewController (inherits from UIViewController). The Unit has a property .viewController and the UnitViewController has a property .object, so in different uses they can refer to each other. These are set when the Plot is opened or new Units are added (or re-added from Undo, etc).
Each UnitViewController has a UIPanGestureRecognizer for its view, and when that gesture occurs, the UnitViewController changes its .object's positionX and positionY values. When that happens, the UnitViewController then observes those changes through KVO and re-positions the view.
This may seem convoluted, but the reason I did it this way is that I can also change the position numerically in a UITableView. Here's an abbreviated version of the code, showing the crucial bits of the path. Some of these methods exist in my UIViewController+LD category, hence the general names.
- (IBAction)dragObject:(UIPanGestureRecognizer *)gesture
{
// GESTURE BEGAN
if ([gesture state] == UIGestureRecognizerStateBegan) {
[self beginGesture:gesture];
if (!_selected) [self setSelected:YES];
}
// turn on registration before last pass
if ([gesture state] == UIGestureRecognizerStateEnded || [gesture state] == UIGestureRecognizerStateCancelled) {
[[NSNotificationCenter defaultCenter] postNotificationName:ENABLE_UNDO_REGISTRATION object:nil];
}
// MOVE
[self dragUnitWithGesture:gesture];
// turn off registration after first pass
if ([gesture state] == UIGestureRecognizerStateBegan) {
[[NSNotificationCenter defaultCenter] postNotificationName:DISABLE_UNDO_REGISTRATION object:nil];
}
// GESTURE ENDED
if ([gesture state] == UIGestureRecognizerStateEnded ||
[gesture state] == UIGestureRecognizerStateCancelled) {
[self endGesture];
}
}
- (void)dragUnitWithGesture:(UIPanGestureRecognizer *)gesture
{
CGPoint translation = [gesture translationInView:self.view.superview];
[self saveNewObjectCenterWithTranslation:translation];
}
- (void)saveNewObjectCenterWithTranslation:(CGPoint)translation
{
[self saveNewObjectCenter:CGPointMake(initialCenter.x + translation.x, initialCenter.y + translation.y)];
}
- (void)saveNewObjectCenter:(CGPoint)center
{
CGPoint dataPoint = [Converter dataPointFromViewPoint:center];
self.object.positionX = [NSNumber numberWithFloat:dataPoint.x];
self.object.positionY = [NSNumber numberWithFloat:dataPoint.y];
}
- (void)beginGesture:(UIGestureRecognizer *)gesture
{
[[NSNotificationCenter defaultCenter] postNotificationName:BEGIN_UNDO_GROUPING object:nil];
self.initialCenter = self.view.center;
}
- (void)endGesture
{
NSError *error = nil;
[self.object.managedObjectContext save:&error];
[[NSNotificationCenter defaultCenter] postNotificationName:END_UNDO_GROUPING object:nil];
}
My issue comes from crash reports obtained through Crashlytics, because I cannot replicate the crash on my devices. There have been multiple reports that all occur with the stack trace:
_UIGestureRecognizerSendActions
-[UnitViewController dragObject:]
-[UnitViewController dragUnitWithGesture:]
-[UnitViewController saveNewObjectCenterWithTranslation:]
-[UIViewController(LD) saveNewObjectCenter:]
_sharedIMPL_setvfk_core + 110
-[NSObject(NSKeyValueObserverNotification) willChangeValueForKey:] + 180
NSKeyValueWillChange + 474
NSKeyValuePushPendingNotificationPerThread + 214
This particular one ended with:
Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x00000000
But I've also seen:
Fatal Exception: NSInternalInconsistencyException
An -observeValueForKeyPath:ofObject:change:context: message was received but not handled (keyPath: positionX)
Fatal Exception: NSObjectInaccessibleException
Core Data could not fulfill a fault for ‘0x00000000 <x-coredata://xxxxxx/Unit/p116>'
So my question: is there a known issue with this type of data modification? Is it simply too fast for the Core Data framework to handle? Or is there something else I could be doing wrong? This problem is only one of the ways Core Data issues have manifested in my app, and I'd love to get to the heart of the matter to make my app more stable.
Update: I don't have enough reputation to post images, but here's a link to the full stack trace: stackTrace