On my Lion app, I have this data model:
The relationship subitem
I agree that there maybe a bug here. I've modified the implementation of the add object >setter to append correctly to a NSMutableOrderedSet.
- (void)addSubitemsObject:(SubItem *)value { NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems]; [tempSet addObject:value]; self.subitems = tempSet; }
Reassigning the set to self.subitems will ensure that the Will/DidChangeValue notifications >are sent.
Leelll, are you sure that after such custom setup of NSMutableOrderedSet values stored in that set will be saved to the database correctly by CoreData? I didn't check that, but it looks like CoreData knows nothing about NSOrderedSet and expects NSSet as to-many relationship container.
Robert,
I agree your answer will work for this, but keep in mind that there is an automatically created method for adding a whole set of values to a relationship already. Apple's Documentation (as seen here under the "To-many Relationships" section or here under the "Custom To-Many Relationship Accessor Methods" section) implements them this way:
- (void)addEmployees:(NSSet *)value
{
[self willChangeValueForKey:@"employees"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:value];
[[self primitiveEmployees] unionSet:value];
[self didChangeValueForKey:@"employees"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:value];
}
- (void)removeEmployees:(NSSet *)value
{
[self willChangeValueForKey:@"employees"
withSetMutation:NSKeyValueMinusSetMutation
usingObjects:value];
[[self primitiveEmployees] minusSet:value];
[self didChangeValueForKey:@"employees"
withSetMutation:NSKeyValueMinusSetMutation
usingObjects:value];
}
You could easily compile your set of relationships outside of core data and then add them all at once using this method. It might be less ugly than the method you suggested ;)
I found a fix for this bug that works for me. I just replace this:
[item addSubitemsObject:subItem];
with this:
item.subitemsObject = subItem;
I just fell foul of this issue, and resolved it using a much simpler implementation than the others outlined here. I simply make use of the methods available on NSManagedObject
for dealing with relationships when not using subclasses.
An example implementation for inserting an entity into an NSOrderedSet
relationship would look like this:
- (void)addAddress:(Address *)address
{
if ([self.addresses containsObject:address]) {
return;
}
// Use NSManagedObject's methods for inserting an object
[[self mutableOrderedSetValueForKey:@"addresses"] addObject:address];
}
This works perfectly, and is what I was using before I moved to NSManagedObject
subclasses.
Personally I have just replaced the calls to the CoreData generated methods with direct calls to the method as outlined in another solution by @Stephan:
NSMutableOrderedSet* tempSet = [self mutableOrderedSetValueForKey:@"subitems"];
[tempSet addObject:value];
[tempSet addObject:value];
This removes the need for categories that might later conflict with a solution from Apple to the generated code when the bug is fixed.
This has the added plus of being the official way to do it!
I think everybody is missing the real problem. It is not in the accessor methods but rather in the fact that NSOrderedSet
is not a subclass of NSSet
. So when -interSectsSet:
is called with an ordered set as argument it fails.
NSOrderedSet* setA = [NSOrderedSet orderedSetWithObjects:@"A",@"B",@"C",nil];
NSSet* setB = [NSSet setWithObjects:@"C",@"D", nil];
[setB intersectsSet:setA];
fails with *** -[NSSet intersectsSet:]: set argument is not an NSSet
Looks like the fix is to change the implementation of the set operators so they handle the types transparently. No reason why a -intersectsSet:
should work with either an ordered or unordered set.
The exception happens in the change notification. Presumably in the code that handles the inverse relationship. Since it only happens if I set an inverse relationship.
The following did the trick for me
@implementation MF_NSOrderedSetFixes
+ (void) fixSetMethods
{
NSArray* classes = [NSArray arrayWithObjects:@"NSSet", @"NSMutableSet", @"NSOrderedSet", @"NSMutableOrderedSet",nil];
[classes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString* name = obj;
Class aClass = objc_lookUpClass([name UTF8String]);
[MF_NSOrderedSetFixes fixMethodWithSetArgument:@selector(intersectsSet:) forClass:aClass];
[MF_NSOrderedSetFixes fixMethodWithSetArgument:@selector(isSubsetOfSet:) forClass:aClass];
}];
}
typedef BOOL (*BoolNSetIMP)(id _s,SEL sel, NSSet*);
/*
Works for all methods of type - (BOOL) method:(NSSet*) aSet
*/
+ (void) fixMethodWithSetArgument:(SEL) aSel forClass:(Class) aClass
{
/* Check that class actually implements method first */
/* can't use get_classInstanceMethod() since it checks superclass */
unsigned int count,i;
Method method = NULL;
Method* methods = class_copyMethodList(aClass, &count);
if(methods) {
for(i=0;i<count;i++) {
if(method_getName(methods[i])==aSel) {
method = methods[i];
}
}
free(methods);
}
if(!method) {
return;
}
// Get old implementation
BoolNSetIMP originalImp = (BoolNSetIMP) method_getImplementation(method);
IMP newImp = imp_implementationWithBlock(^BOOL(NSSet *_s, NSSet *otherSet) {
if([otherSet isKindOfClass:[NSOrderedSet class]]) {
otherSet = [(NSOrderedSet*)otherSet set];
}
// Call original implementation
return originalImp(_s,aSel,otherSet);
});
method_setImplementation(method, newImp);
}
@end