在 Core Data 中改变 NSOrderedSet 顺序的考验和磨难

Posted

技术标签:

【中文标题】在 Core Data 中改变 NSOrderedSet 顺序的考验和磨难【英文标题】:Trials and tribulations of changing NSOrderedSet order in Core Data 【发布时间】:2015-07-12 11:21:51 【问题描述】:

我有一个NSOrderedSet 附加到一个传递给UITableViewController 的Core Data 对象。 managedObjectContextUITableViewControllermyNSOrderedSet 中正常显示我的tableView。当我去重新订购myNSOrderedSet 时,应用程序崩溃了。我在moveRowAtIndexPath 中设置了一个断点,以查看发生了什么,一切都很好,直到我尝试保存它。

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 
    // BREAKPOINT: "po self.routineToEdit.exercises" shows order is 0, 1, 2
    NSMutableOrderedSet *orderedSet = [self.routineToEdit.exercises mutableCopy];

    [orderedSet exchangeObjectAtIndex:fromIndexPath.row withObjectAtIndex:toIndexPath.row];

    // BREAKPOINT: "po self.routineToEdit.exercises" shows order is 2, 1, 0
    self.routineToEdit.exercises = orderedSet;

    [self saveContext];


- (void)saveContext 
    if (self.managedObjectContext != nil) 
        NSError *error = nil;
        if ([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&error]) 
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        
    

我不想将我的对象传递给MyUITableViewController,而是使用fetchedResultsController 来抓取我的对象并将其显示在MyUITableViewController 上。在我走这条路之前,我想看看我可以用从AnotherUITableViewController 传入的对象重新订购NSOrderedSet

这是我崩溃时的控制台输出:

2015-07-12 06:39:53.176 MyApp[26505:1105589] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString appendString:]: nil argument'
*** First throw call stack:
(
    0   CoreFoundation                      0x00000001060dec65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x0000000105663bb7 objc_exception_throw + 45
    2   CoreFoundation                      0x00000001060deb9d +[NSException raise:format:] + 205
    3   CoreFoundation                      0x00000001060afabf mutateError + 159
    4   CoreData                            0x000000010591ec26 -[_NSSQLGenerator prepareMasterReorderStatementPart2ForRelationship:] + 118
    5   CoreData                            0x000000010598cfb8 -[NSSQLAdapter newCorrelationMasterReorderStatementPart2ForRelationship:] + 72
    6   CoreData                            0x00000001059a5671 -[NSSQLiteConnection writeCorrelationMasterReordersFromTracker:] + 817
    7   CoreData                            0x00000001059a5f81 -[NSSQLiteConnection writeCorrelationChangesFromTracker:] + 65
    8   CoreData                            0x0000000105997627 -[NSSQLCore writeChanges] + 1351
    9   CoreData                            0x00000001058d32c7 -[NSSQLCore saveChanges:] + 423
    10  CoreData                            0x00000001058a3e74 -[NSSQLCore executeRequest:withContext:error:] + 484
    11  CoreData                            0x000000010597ee3f __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke + 4335
    12  CoreData                            0x0000000105987c30 gutsOfBlockToNSPersistentStoreCoordinatorPerform + 192
    13  libdispatch.dylib                   0x0000000108890614 _dispatch_client_callout + 8
    14  libdispatch.dylib                   0x0000000108876002 _dispatch_barrier_sync_f_invoke + 365
    15  CoreData                            0x0000000105979245 _perform + 197
    16  CoreData                            0x00000001058a3a58 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 504
    17  CoreData                            0x00000001058cd52d -[NSManagedObjectContext save:] + 1213
    18  MyApp                           0x000000010511083e -[EditRoutineTableViewController saveContext] + 222
    19  MyApp                           0x0000000105110673 -[EditRoutineTableViewController tableView:moveRowAtIndexPath:toIndexPath:] + 371
    20  UIKit                               0x0000000106817822 -[UITableView _endReorderingForCell:wasCancelled:animated:] + 565
    21  UIKit                               0x000000010682b89d -[UIControl touchesEnded:withEvent:] + 462
    22  UIKit                               0x0000000106767958 -[UIWindow _sendTouchesForEvent:] + 735
    23  UIKit                               0x0000000106768282 -[UIWindow sendEvent:] + 682
    24  UIKit                               0x000000010672e541 -[UIApplication sendEvent:] + 246
    25  UIKit                               0x000000010673bcdc _UIApplicationHandleEventFromQueueEvent + 18265
    26  UIKit                               0x000000010671659c _UIApplicationHandleEventQueue + 2066
    27  CoreFoundation                      0x0000000106012431 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    28  CoreFoundation                      0x00000001060082fd __CFRunLoopDoSources0 + 269
    29  CoreFoundation                      0x0000000106007934 __CFRunLoopRun + 868
    30  CoreFoundation                      0x0000000106007366 CFRunLoopRunSpecific + 470
    31  GraphicsServices                    0x000000010a12ba3e GSEventRunModal + 161
    32  UIKit                               0x00000001067198c0 UIApplicationMain + 1282
    33  MyApp                           0x000000010510a05f main + 111
    34  libdyld.dylib                       0x00000001088c4145 start + 1

感谢您的阅读。我欢迎您的意见。

我发现这个问题在我遇到的问题的“同一区域”,但是我的集合顺序不像示例中给出的示例那样是静态的。

Core Data ordering with a UITableView and NSOrderedSet

【问题讨论】:

【参考方案1】:

我认为获取结果控制器在这里没有给你任何优势(主要是由于排序要求在这里没有意义)。

我将直接使用有序集(正在编辑的对象的一对多关系)来通知您的表格视图数据源。我会检查将新排序的有序集分配回对象是否按预期工作。

【讨论】:

感谢您帮助我排除了替代方法。在我保存到managedObjectContext 之前,我仍然很难理解为什么一切似乎都按预期执行。在这一点上,我敢打赌,我是一个方法和/或一两行代码,无法让它发挥作用。【参考方案2】:

*** 的共识似乎存在一个错误,即 Core Data 如何处理NSOrderedSet 关系。我能够使用 Dmitry Makarenko 关于 Xcode 生成的访问器的帖子中包含的信息来操纵 NSOrderedSet 的顺序:

Exception thrown in NSOrderedSet generated accessors

我还清理了(至少我是这么认为的)我用于在UITableView 中移动行的代码:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 
    NSMutableOrderedSet *orderedSet = [self.routineToEdit mutableOrderedSetValueForKey:@"stretches"].mutableCopy;

    // This is necessary, as exchangeObjectAtIndex:withObjectAtIndex doesn't work passing in indexPath.row
    NSInteger fromIndex = fromIndexPath.row;
    NSInteger toIndex = toIndexPath.row;

    // Swap objects at indexes
    [orderedSet exchangeObjectAtIndex:fromIndex withObjectAtIndex:toIndex];
    NSLog(@"ending orderedSet = /n%@", orderedSet);

    // Assign NSMutableOrderdSet to the object's NSOrderedSet
    self.routineToEdit.stretches = orderedSet;

    // Added performBlockAndWait so the save is complete before doing anything else
    [self.persistentStoreCoordinator performBlockAndWait:^
        [self saveContext];
    ];


在 UITableView 行/NSObjects 被交换后立即将对象保存到managedObjectContext 是至关重要的——当我点击完成IBAction w/save 方法时,我发现并非所有更改都被保存时,我发现了这一点在那里调用:

[self.persistentStoreCoordinator performBlockAndWait:^
            [self saveContext];
        ];

...但应用程序仍然崩溃。

最后,我将 BOTH 实体之间的关系更改为有序,这样就成功了:

crash on coredata ios8

就我而言,这就足够了。我独立尝试了这些修复程序中的每一个,但没有一个是独立运行的。此时,我的应用程序按我想要的方式运行,所以我暂时不用管它。

【讨论】:

【参考方案3】:

拨打mutableOrderedSetValueForKey:你已经成功了一半,不要拨打mutableCopy就可以了!此外,不要将此可变有序集重新分配回exercises。只需保存您的上下文,在上下文中调用refreshObject:mergeChanges:exercises 应该会反映您的更改。

【讨论】:

感谢您的意见!那是我没有尝试过(并且应该尝试过)的事情。我现在可以使用它,但是当我将其保存到managedObjectContext 时遇到了问题。当我在交换索引后输入po self.routineToEdit.exercises 时,它打印出我的预期。我应该在[self.moc save:&error] 之前refreshObject:mergeChanges: 吗? refreshObject:mergeChanges: 应该在保存后调用。它告诉对象从存储中重新加载新值。 谢谢。我将它纳入了我今天早上拼凑的解决方案中。

以上是关于在 Core Data 中改变 NSOrderedSet 顺序的考验和磨难的主要内容,如果未能解决你的问题,请参考以下文章

重命名应用程序后,Core Data 出现问题?

如何在 Core Data 中设置一对多关系的排序?

如何将数据提供给 iPhone 应用程序以转换为 Core Data

Swift 3.0 使用Core Data

从 Obj C 用 Swift 重写应用程序时使用 Core Data

如何在不丢失数据的情况下更改 Core Data SQLite 数据库的结构