具有KVO视图控制器的Core Data对象和基于手势的更改

时间:2014-02-07 14:45:32

标签: ios objective-c core-data uigesturerecognizer key-value-observing

我有一个iPhone / iPad应用程序,其中一个主要功能是在绘图上排列对象。我使用Core Data来管理对象的关系;对于这个例子,我将讨论单元,它与一个Plot(和一个到多个反向)有一对一的关系。单位的属性包括positionX,positionY和angle。

每个Unit(继承自NSManagedObject)与UnitViewController(继承自UIViewController)配对。 Unit有一个属性.viewController和UnitViewController有一个属性.object,因此在不同的用途中它们可以互相引用。这些是在打开Plot或添加新单位(或从Undo等重新添加)时设置的。

每个UnitViewController都有一个UIPanGestureRecognizer用于其视图,当该手势发生时,UnitViewController会更改其.object的positionX和positionY值。当发生这种情况时,UnitViewController然后通过KVO观察这些变化并重新定位视图。

这可能看似令人费解,但我这样做的原因是我也可以在UITableView中以数字方式更改位置。这是代码的缩写版本,显示了路径的关键部分。其中一些方法存在于我的UIViewController + LD类别中,因此是通用名称。

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

我的问题来自通过Crashlytics获得的崩溃报告,因为我无法在我的设备上复制崩溃。有多个报告都发生在堆栈跟踪中:

_UIGestureRecognizerSendActions
-[UnitViewController dragObject:]
-[UnitViewController dragUnitWithGesture:]
-[UnitViewController saveNewObjectCenterWithTranslation:]
-[UIViewController(LD) saveNewObjectCenter:]
_sharedIMPL_setvfk_core + 110
-[NSObject(NSKeyValueObserverNotification) willChangeValueForKey:] + 180
NSKeyValueWillChange + 474
NSKeyValuePushPendingNotificationPerThread + 214

这个特别的结尾是:

Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x00000000

但我也看到了:

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>'

所以我的问题是:这种类型的数据修改是否存在已知问题?对于Core Data框架来说,处理它的速度是否过快?或者还有什么我可能做错了吗?这个问题只是Core Data问题在我的应用程序中出现的方式之一,我很想了解问题的核心,以使我的应用程序更稳定。

更新 我没有足够的声誉来发布图片,但这里是完整堆栈跟踪的链接: stackTrace

3 个答案:

答案 0 :(得分:1)

尽管我同意Marcus关于减少save:电话数量的精神,但我并不相信save:频率是这个特定问题的根源,因为它仅在潘手势结束。

相反,崩溃日志截图看起来非常像KVO观察者已经被释放,而没有首先作为观察者被移除。

检查您的代码并确保所有addObserver:forKeyPath:context:调用均由removeObserver:forKeyPath:context:进行平衡,并且在未先删除其观察结果的情况下,您的观察者无法取消分配。

如果你无法发现可能出现问题的地方,那么可能会编辑你的问题以包含与KVO相关的代码,以便我们查看它。

对于could not fulfill a fault异常,这是一个不同的问题,可能需要一个不同的堆栈溢出问题,完整的堆栈跟踪。

答案 1 :(得分:0)

核心数据可以按照您想要的任何速度处理大量数据。但是,您的代码可能正在做更多的工作。

首先,您似乎在每次搬家时都保存。这完全没必要。无论是否保存实体,核心数据的工作方式都相同。但是,保存到磁盘昂贵。因此,如果您保存每次移动/更改,都可能导致问题。删除这些保存只会有所帮助。考虑仅在用户期望暂停时保存:离开应用程序,更改视图等等。

第二,这次崩溃是由什么线索引起的?如果你有一个多线程的情况,并且你在错误的线程上收到通知,你可能会遇到一些奇怪的崩溃。 UI期望/要求您仅在主线程(线程0)上与其进行交互。核心数据也有一些围绕线程的规则。如果这是一个问题,我无法从您的堆栈跟踪片段中分辨出来。发布更多的堆栈跟踪将有所帮助。

更新

保存通常会损害用户体验,因为您在每次保存时都阻止了主线程。虽然它不应该直接导致崩溃,但它理论上可以导致通知“排队”等待写入完成。最好在每次发出通知时保存每X秒或其他东西。

你可以发布完整的堆栈跟踪吗?您展示的代码段删除了大量有助于诊断问题的信息。

至于不重复,这往往也指向线程问题。并非每个CPU都是相同的。线程问题可能出现在一小部分设备上,而在其他设备上根本不可复制。我曾经在单个设备上显示线程问题。

更新

进一步反思。将用户事件连接到Core Data中的保存只是糟糕的事情。无法保证用户会做任何理智的事情。让用户事件启动计时器会好得多。如果计时器已经存在,那么再将它踢出去。当计时器触发时,您保存。

这会给你一些断开连接并保护你免受用户行为的侵害。

答案 2 :(得分:0)

<强>回答

经过大量挖掘和修复其他错误后,我想我已经找到了问题所在。我还没有解决它,但我能够重复崩溃的行为,所以它只需要花多少小时来解决它。

问题出在一个构思错误的撤销分组,它取消了一部分动作而不是全部动作,当该分组撤消时,可能会发生许多不同的崩溃,具体取决于下一步执行的操作。基本上,我留下了一个不再存在的故障NSManagedObject子类实例,并且对这些其他操作作出反应的视图控制器试图从各个地方调用它。当发生这种情况时,应用程序显然会崩溃,但由于堆栈跟踪中的任何操作都没有表明问题的实际原因,因此很难追踪。

所以这与保存频率无关,这很好。我仍然很好奇,看看它是否能阻止我认为在另一个线程上发生的Core Data崩溃。

感谢所有回答的人!

<强>更新

实际问题在其他地方。请在此处查看我的其他问题以查看答案和解决方案: make NSUndoManager ignore a property of an NSManagedObject