0

I'm using CoreData to persist a list of messages in a conversation.

Conversation is a managedObject that has an array of Messages. In one scenario, I'm trying to delete all the messages in a conversation.

for (UQMessage * message in self.tempConversation.chatMessages){
            [self.tempConversation.managedObjectContext deleteObject:message];
            error = nil;
            [self.tempConversation.managedObjectContext.persistentStoreCoordinator lock];

            if (![self.tempConversation.managedObjectContext save:&error]) {
                NSLog(@"Can't Delete! %@ %@", error, [error localizedDescription]);
                return;
            }
            [self.tempConversation.managedObjectContext.persistentStoreCoordinator unlock];
}

When I check for

self.tempConversation.chatMessages.count

Nothing changes.

Everything works perfectly well when I try to add messages, and when I delete the conversation itself. But I can't seem to delete a single message.

Is it even possible to do since I'm not trying to delete the managed object itself but another object inside it? If not, Anyway around it?

EDIT:

Messages is an NSOrderedSet inside Conversation. I've found this works (taken from this thread):

NSMutableOrderedSet *mutableItems = (NSMutableOrderedSet *)items.mutableCopy;
[mutableItems addObject:anItem];
items = (NSOrderedSet *)mutableItems.copy;

though I'm not sure if this is the way to go.

Community
  • 1
  • 1
U_D
  • 711
  • 1
  • 10
  • 18

3 Answers3

2

First, about the answer by Matt S., you are not modifying self.tempConversation so you don't have to worry about mutating the array while iterating.

On the other hand, if your problem is that self.tempConversation.chatMessages.count doesn't change. That is normal. You are deleting objects from the NSManagedObjectContext. But the array is not modified. So, the array still have the managed object BUT that managed object is deleted. Is that easy. It is a zombie managed object because it has been deleted from the MOC. Nevertheless the object has not been removed from the array. So you have a managed object with the property deleted set to YES. And it is not part of the MOC any more.

Gabriel
  • 3,319
  • 1
  • 16
  • 21
  • The OP is attempting to modify self.tempConversation.chatMessages. It’s not as straightforward as a pure removal of the item from the array, but because the OP is attempting to fast enumerate the array, lock the persistent store, commit the deletions to the relationship, and then keep iterating the relationship array, the OP is effectively attempting to mutate the relationship while enumerating the array. – Matt S. Jul 06 '15 at 13:22
  • Honestly I'm surprised it's not crashing because theoretically when you commit a save like that, Core Data increments an internal version of the MOC and should return all the existing relationships to fault objects to be re-fetched at first access. My guess is fast enumeration does a deep copy on the object and as such tries to guarantee not crashing. – Matt S. Jul 06 '15 at 13:24
  • @MattS. I don't understand why you say the OP is attempting to dimity self.tempConversation.chatMessages. There aren't any operation modifying that array. – Gabriel Jul 06 '15 at 13:46
  • It's an implicit mutation by what the code is attempting to do and what core data should be doing. By running a deletion and THEN saving that deletion, if fast enumeration wasn't being nice, the array should contain different results on the next pass through. – Matt S. Jul 06 '15 at 14:03
  • Where do you find an implicit mutation? if self.tempConversation.chatMessages is an array or a copy of an ordered set, even if the original i managed by core data, nobody is deleting or inserting items in the array. – Gabriel Jul 06 '15 at 14:11
1

You should never, ever mutate the array you're iterating over. Per the fast enumeration docs: "It is not safe to remove, replace, or add to a mutable collection’s elements while enumerating through it. If you need to modify a collection during enumeration, you can either make a copy of the collection and enumerate using the copy or collect the information you require during the enumeration and apply the changes afterwards."

The result of mutating an array during enumeration is undefined, and my guess is core data might be just tossing up its hands and not doing anything. The reason why the mutable copy works is because you're working on a copy, not the set you're enumerating over.

I would rewrite your logic to follow the guidelines laid down in the enumeration docs, and make your changes outside of the loop.

EDIT: Additional Thoughts

  • Why are you locking & unlocking the persistent store? It handles that itself.

  • You can probably call delete safely inside the for in (but I wouldn't) and then call save outside, since save is what actually does the deletion.

More Thoughts

(Transcribing from a comment) - After thinking about this for a few days and then coming back, my guess is the reason you're not outright crashing is fast enumeration is doing a deep copy on the relationship array you're working on, because calling save on a MOC is going to increment an internal version, and then should return all existing managed objected to a faulted object to be re-fetched on next access. Really this code you've got here is actually quite dangerous from an "application health" perspective.

Matt S.
  • 1,882
  • 13
  • 17
0

If you look at the documentation for core data relationships, I think you'll find the easier thing to do is just set the relationship delete rule for the relationship to "Cascade". This will remove all the messages for you when you delete the conversation. Here's the reference: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW1

dudeman
  • 1,106
  • 8
  • 19
  • That's assuming the OP wants to delete the original conversation to which the messages are related ;-) – Matt S. Jul 06 '15 at 13:36