1
votes

Update: This thread identifies a bug in NSCollectionView when the represented objects of the collection view are NSManagedObjects. The bug is triggered as follows;

(a) Delete objects from the NSArrayController (b) Perform save on the related nsmanagedobjectcontext any time after the deletion and before the NSCollectionView has finished its animation.

These projects on github demonstrate the issue.

https://github.com/artifacts/NSCollectionViewCoreDataBug https://github.com/iracooke/CoreDataCollectionViewCrashing

Original question below

I have an NSCollectionView setup with its content binding bound to arrangedObjects of an NSArrayController of core-data entities. In my Collection Item View (the prototype for the NSCollectionView view) I have several controls bound via representedObject of my collectionview item to my core-data entities.

For the most part this works OK.

I encounter an objc_exception when I try to delete entities from my ArrayController. I delete these entities simply by calling;

[myArrayController removeObject:managedObjectToDelete];

Unfortunately, when I do this I frequently get a "CoreData could not fulfill a fault" Error .. with the responsible entity being one of the entities being managed by the NSArrayController.

Examination of the call-stack when the exception is thrown reveals that the crash originates when the NSCollectionView receives its _endOfAnimation method. This in turn initiates other methods to do with unbinding (likely of properties of my entity with controls in my view).

One further bit of info is that the Entity I'm working with has no relationships to other entities in my model.

It looks to me as though the following problem is occurring;

  • When I delete objects from my NSArrayController they are in turn deleted from the context.
  • After deletion from the context the objects are turned into faults
  • The NSCollectionView has retained references to the objects (now faults). It tries to clean them up at the end of its animation (unbinding etc).
  • When NSCollectionView tries to cleanup bindings to the object it causes core data to attempt to fire a fault on the object (hope I got my terminology right there). This causes an error because the object has not yet been saved to disk.

The only way I can think of to prevent this, is to persist the objects in the store (by saving them) prior to deletion. This works, but only in a hackish kind of way since I need to ensure that one round of deletion is complete before saving again ... and since the error occurs during an animation .. after some delay .. and two saves in succession would result in the same error occuring again.

Does this mean that I cannot used a Core-Data backed NSArrayController to populate an NSCollectionView? If not what am I doing wrong? Is there a better way around this issue?

2
I've set up another project demonstrating the problem and filed a bug, it's radar #9903150. You'll find the project and bug description here: github.com/artifacts/NSCollectionViewCoreDataBuguser523214
Thanks for that. Your github project is a nicer reproduction of the bug than mine. I guess Apple have a pretty good description of the problem now from this thread.Ira Cooke

2 Answers

2
votes

A direct yet boring answer: I can confirm that a Core Data backed NSArrayController can populate an NSCollectionView, with different GUI objects in the collection NSView item bound to the generated "Collection View Item" and referring to various Core Data objects along the path. Programmatically deleting (and re-ordering) elements of the NSArrayController works (with animation for free).

Perhaps some bindings or other dependencies inside your Collection View are causing the problem? Or a threading issue with the Managed Object Context(s)?

1
votes

You can circumvent this problem by adding a (assign) property to your CollectionItem which points to the corresponding ArrayController.

This property can be set in -[YourNSCollectionViewSubClass newItemWithRepresentedObject:].

Then you can observe arrangedObjects in each item. When it changes and item.representedObject is not contained in arrangedObjects any more, you set item.representedObject to nil. In my testing (10.6.8), this triggers the binding cleanup before CoreData turns the objects into faults. (My objects have relationships and the item view has bindings into them).

btw: This problem is not restricted to saving during animation, undo/redo/save combinations can trigger it too.

I was looking for a good place to start the observation inside the item but the only one I came up with was copyWithZone: which I wanted to avoid. (-awakeFromNib is called only for the first item, -view is too early).

Therefore I (reluctantly) decided to start the observation in -newItemWithRepresentedObject: and stop it in the item's -dealloc.

You should also watch out for any controls or other actions triggered by objects in the item view - by rapid clicking I could cause messages to deallocated objects, presumably because the still reacting buttons where animating away under the mouse. My solution is to disable the controls when setting representedObject to nil. YMMV.

Here is my observer code:

    - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if( object == self.arrayController && [keyPath isEqualToString: @"arrangedObjects"] ) {
        if( NO == [self.arrayController.arrangedObjects containsObject: self.representedObject] ) {
            self.representedObject = nil;

            for(NSView *subview in [self.view subviews]) {
                if( [subview isKindOfClass: [NSControl class]] ) {
                    [(NSControl *)subview setEnabled: NO];
                }
            }
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject: object change:change context:context];
    }
}