iOS Core Data to-many relationship breaks when removing an object using the generated accessors
I have an iOS 4 + app, which is now being retrofitted to use Core Data. I have an Entity "Article" which has a to-many relationship to another Entity "MediaResource" and I generated NSManagedObject subclasses for each. The relationship is called "media" and is set to be optional, and to Cascade delete the associated MediaResource objects. There is an inverse to-one back to the Article.
The generated code included a property of type NSSet * media in the Article class, as well as these methods:
- (void)addMediaObject:(MediaResource *)value {
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:@"media" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[[self primitiveValueForKey:@"media"] addObject:value];
[self didChangeValueForKey:@"media" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[changedObjects release];
}
- (void)removeMediaObject:(MediaResource *)value {
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:@"media" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
[[self primitiveValueForKey:@"media"] removeObject:value];
[self didChangeValueForKey:@"media" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
[changedObjects release];
}
- (void)addMedia:(NSSet *)value {
[self willChangeValueForKey:@"media" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
[[self primitiveValueForKey:@"media"] unionSet:value];
[self didChangeValueForKey:@"media" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
}
- (void)removeMedia:(NSSet *)value {
[self willChangeValueForKey:@"media" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
[[self primitiveValueForKey:@"media"] minusSet:value];
[self didChangeValueForKey:@"media" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
}
There are no NSMutableSets here anywhere, but I assume the auto-generated code knows what it is doing.
I can add an article, then add a MediaResource object to it, and do it like this:
[newArticle addMediaObject:newMediaObject];
I gather from the docs that is the correct way. However, I also see reference to using mutableSetValueForKey:
but that does not seem to apply here.
Basically, what happens is that the during a given run, I can add an article, then remove it, and it works fine. However, if I then a开发者_如何转开发dd it, quit the app then re-run it, then remove it, I get an exception where it is telling an NSSet to remove an object, which of course it can't do. As I didn't actually write these methods, I am confused. Any ideas? Here is the relevant stack trace:
2011-08-16 11:12:32.141 MyApp[41825:207] -[__NSSet0 removeObject:]: unrecognized selector sent to instance 0x8041f60
2011-08-16 11:12:32.144 MyApp[41825:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSSet0 removeObject:]: unrecognized selector sent to instance 0x8041f60'
*** Call stack at first throw:
(
0 CoreFoundation 0x01a5b5a9 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x01baf313 objc_exception_throw + 44
2 CoreFoundation 0x01a5d0bb -[NSObject(NSObject) doesNotRecognizeSelector:] + 187
3 CoreFoundation 0x019cc966 ___forwarding___ + 966
4 CoreFoundation 0x019cc522 _CF_forwarding_prep_0 + 50
5 CTKennedy 0x000b292c -[Article removeMediaObject:] + 220
6 CoreData 0x0054a1f2 -[NSManagedObject(_NSInternalMethods) _excludeObject:fromPropertyWithKey:andIndex:] + 98
7 CoreData 0x0053f7d1 -[NSManagedObject(_NSInternalMethods) _maintainInverseRelationship:forProperty:oldDestination:newDestination:] + 449
8 CoreData 0x00593b55 -[NSManagedObject(_NSInternalMethods) _propagateDelete:] + 1541
9 CoreData 0x0054a02a -[NSManagedObject(_NSInternalMethods) _propagateDelete] + 42
10 CoreData 0x00549e53 -[NSManagedObjectContext(_NSInternalChangeProcessing) _propagateDeletesUsingTable:] + 515
11 CoreData 0x00549c12 -[NSManagedObjectContext(_NSInternalChangeProcessing) _processDeletedObjects:] + 146
12 CoreData 0x0053cba8 -[NSManagedObjectContext(_NSInternalChangeProcessing) _propagatePendingDeletesAtEndOfEvent:] + 104
13 CoreData 0x00508982 -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 754
14 CoreData 0x00542715 -[NSManagedObjectContext save:] + 149
Your problem isn't being caused by the autogenerated code. You don't see a mutable set because Core Data actually uses a class cluster for NSSet and returns anther class _NSFaultingMutableSet
when a to-many relationship is accessed. The NSSet is most likely used in the header to keep other classes from attempting to set the relationship without using the defined methods.
I have a generated class Alpha
setup with a to-many relationship betas
to a class Beta
. If I put a breakpoint on the first line of this method:
-(void)addBetasObject:(NSManagedObject *)value {
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:@"betas" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[[self primitiveValueForKey:@"betas"] addObject:value];
[self didChangeValueForKey:@"betas" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[changedObjects release];
}
... I can then do this at the debugger prompt:
(gdb) po [self primitiveValueForKey:@"betas"]
Relationship objects for {(
)} on 0x4d45e20
(gdb) po [[self primitiveValueForKey:@"betas"] class]
_NSFaultingMutableSet
So, despite the header, the property is a mutable set albeit a custom one.
Likewise, with the matching method:
- (void)removeBetasObject:(NSManagedObject *)value {
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:@"betas" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
[[self primitiveValueForKey:@"betas"] removeObject:value];
[self didChangeValueForKey:@"betas" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
[changedObjects release];
}
... after a save, the debugger gives:
(gdb) po [self primitiveValueForKey:@"betas"]
Relationship objects for {(
<Beta: 0x4d4be40> (entity: Beta; id: 0x4d52510 <x-coredata://64E86DEC-53DA-4197-B1F3-6C0007576100/Beta/p1> ; data: {
alphas = (
"0x4d52140 <x-coredata://64E86DEC-53DA-4197-B1F3-6C0007576100/Alpha/p1>"
);
num = 0;
})
)} on 0x4d45e20
(gdb) po [[self primitiveValueForKey:@"betas"] class]
_NSFaultingMutableSet
This behind the scenes change in class happens because of the @dynamic
processor directive for betas
i.e. @dynamic betas;
and if that line gets removed, then the attributes will be defined purely as they are in the header.
Check the Article.m
file for @dynamic media;
If it is missing or is @synthesize media;
that is the source of your problem.
精彩评论