One problem with objects being so closely tied to the underlying DB in Core Data.

Why does the following code throw an exception and immediately sig kill on the second iteration at [e nextObject]:

		for (NSManagedObject *current in categories) {
			if ([lowercaseName isEqualToString:[current.name lowercaseString]] && category != current ) {
				
				// Move all the recipes.
				NSEnumerator *e = [[category valueForKey:@"recipe"] objectEnumerator];
				id object;
				while (object = [e nextObject]) {
					[(NSManagedObject *)object setCategory:current];
				}
				// Delete the category we imported.
				[moc deleteObject:category];
				
				// Reassign the current category to be returned instead.
				category = current;
				break;
			}
		}

Please note that in the above code, implied but not shown, category is an NSManagedObject and categories is an NSSet of NSManagedObjects. Also category will ALWAYS be present in categories.

The code above is intended to stop duplication of an NSManagedObject by finding out if one already exists and if it does moving it’s objects over to it.

The reason it crashes is because these objects (being NSManagedObjects) are very closely tied to the underlying database. After the first iteration through the while loop we have changed the underlying database, this change is immediately reflected in our NSManagedObject and therefore immediately reflected in the iterator. For example:

1) while (object = [e nextObject]) {

The iterator ‘e’ is attached to the managed object ‘category’, it contains 2 objects. The first of which is assigned to ‘object’.

2) [(NSManagedObject *)object setCategory:current];

The managed object ‘object’ is re-assigned away from the managed object ‘category’ to the managed object ‘current’. The managed object ‘category’ therefore now only has one object.

3) while (object = [e nextObject]) {

An exception is thrown. The NSEnumerator (actually an NSFastEnumerator something or other) can also be written in code as:

for (object in category)

Internally it behaves very very much like a for loop using pointer arithmetic. For this reason on the second loop the pointer is no longer valid as what was object no. 2 is now object no. 1. We have moved the object to another NSManagedObject and the change is reflected immediately. The contents of the while loop can be modified to anything that does not move the objects within ‘category’ and the crash goes away.

What do we do about this? Well the first thought is to work on a copy of the data. Thankfully we are given very simple tools to make copies such as:

NSSet *fishes = [[atlantic fish] copy];
NSMutableSet *fishes = [[pacific fish] mutableCopy];

Right? In this instance yes. Be aware however that this will not always solve your problems. It might give you a copy of the surface data but it does NOT give you a deep copy of the underlying objects. So fishes is not the same as atlantic but it will contain the same ‘fish’. If you follow my rather horrible example. I expect it’s influenced by the tropical fish tank and conversation I was having about Danio’s whilst I write this. The fixed code reads:

		for (NSManagedObject *current in categories) {
			if ([lowercaseName isEqualToString:[current.name lowercaseString]] && category != current ) {
				
				// Move all the recipes.
				NSSet *workingCopy = [[category item] copy];
				NSEnumerator *e = [[category valueForKey:@"item"] objectEnumerator];
				id object;
				while (object = [e workingCopy]) {
					[(NSManagedObject *)object setCategory:current];
				}
				// Delete the category we imported.
				[workingCopy release];
				[moc deleteObject:category];
				
				// Reassign the current category to be returned instead.
				category = current;
				break;
			}
		}
This entry was posted in Programming and tagged , . Bookmark the permalink.