Exception thrown in NSOrderedSet generated accessors
Asked Answered
L

25

363

On my Lion app, I have this data model:

enter image description here

The relationship subitems inside Item is ordered.

Xcode 4.1 (build 4B110) has created for me the file Item.h, Item.m, SubItem.h and SubItem.h.

Here is the content (autogenerated) of Item.h:

#import <Foundation/Foundation.h>

#import <CoreData/CoreData.h>

@class SubItem;

@interface Item : NSManagedObject {
@private
}

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSOrderedSet *subitems;
@end

@interface Item (CoreDataGeneratedAccessors)

- (void)insertObject:(SubItem *)value inSubitemsAtIndex:(NSUInteger)idx;
- (void)removeObjectFromSubitemsAtIndex:(NSUInteger)idx;
- (void)insertSubitems:(NSArray *)value atIndexes:(NSIndexSet *)indexes;
- (void)removeSubitemsAtIndexes:(NSIndexSet *)indexes;
- (void)replaceObjectInSubitemsAtIndex:(NSUInteger)idx withObject:(SubItem *)value;
- (void)replaceSubitemsAtIndexes:(NSIndexSet *)indexes withSubitems:(NSArray *)values;
- (void)addSubitemsObject:(SubItem *)value;
- (void)removeSubitemsObject:(SubItem *)value;
- (void)addSubitems:(NSOrderedSet *)values;
- (void)removeSubitems:(NSOrderedSet *)values;

@end

And here is the content (autogenerated) of Item.m:

#import "Item.h"
#import "SubItem.h"

@implementation Item

@dynamic name;
@dynamic subitems;

@end

As you can see, the class Item offers a method called addSubitemsObject:. Unfortunately, when trying to use it in this way:

Item *item = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:self.managedObjectContext];
item.name = @"FirstItem";

SubItem *subItem = [NSEntityDescription insertNewObjectForEntityForName:@"SubItem" inManagedObjectContext:self.managedObjectContext];

[item addSubitemsObject:subItem];

this error appear:

2011-09-12 10:28:45.236 Test[2002:707] *** -[NSSet intersectsSet:]: set argument is not an NSSet

Can you help me?

Update:

After just 1,787 days from my bug report, today (August 1, 2016) Apple wrote me this: "Please verify this issue with the latest iOS 10 beta build and update your bug report at bugreport.apple.com with your results.". Let's hope this is the right time :)

Laconic answered 12/9, 2011 at 8:59 Comment(18)
The addSubitemsObject:method accept a SubItem, not a NSSet, as you can see in the declaration (Item.h).Laconic
I'm seeing same issue. Hopefully it gets fixed soon. Although using the mutable ordered set directly is an easy workaround for the time being. Note: I am using mogenerator, but I assume it is using the same Apple generator internally for this portion of the generated code.Swallowtailed
I'm hitting this same bug. Can anyone confirm I can access the Ordered Set directly without problems? Is there nothing important going on in the auto-generated methods that I will bypass through direct access.Bespectacled
I'm running into the same problem. 3rd major issue I've run into with Core Data. Making me wonder if its worth it. If I have to write my own scaler accessors and relationship accessors, not much left that its providing.Vedavedalia
Running into the same issue. It looks like it is caused by the back-reference to item from subitem. If I remove it it works just fine.Deluna
It is almost 2 years! Will you fix it in iOS 7, Apple? —— I just want to share with those wondering whether this bug is still there:"Yes, it is."Saccharo
Very close to two years now, this is still an issue in all xcode 5 developer previews.Centaury
Why is this still not addressed? Does anybody know? Is it really that hard to fix this bug?...Nellie
Do you still see the problem if you use the appropriate KVC accessor? (i.e. mutableOrderedSetValueForKey:)Adamandeve
Appears to still be an issue on Mavericks.Floriculture
alomost 3 years. still there.Diphenylhydantoin
and with Yosemite!! still there, when they are gonna fix it!!Seasoning
well actually one should use mutableOrderedSetValueForKey as mentioned here https://mcmap.net/q/92499/-exception-thrown-in-nsorderedset-generated-accessorsAurochs
and still there! iOS 8.1.3, xcode 6.1.1!Regeneracy
Still crashing on Yosemite 10.10.5 and Xcode 7.1.Imagery
Swift bug filed: bugs.swift.org/browse/SR-468Compander
Observing the same issue with Xcode 7.2.1, iOS 9.2Idaline
Crashed in ios 10.0 Xcode8.0Spongioblast
S
262

I reproduced your setup both with your data model and one of my own with different names. I got the same error in both cases.

Looks like a bug in Apple's autogenerated code.

Sputter answered 13/9, 2011 at 0:24 Comment(32)
+1 For the bug number. @Laconic please provide it here. I does NOT seem to be fixed neither in 4.2.1 nor in XCode 4.3 PreviewNotebook
The bug ID is 10114310. It was reported on 13-Sep-2011 but today (15-Jan-2012) it is still "open". It is incredible, considering the number of people who have the same problem.Laconic
Having the same issue, updated from Xcode 3 to 4.x.. It is very frustrating.Harelip
have same issue ... can you put the bug on openradarFuture
The bug is actually related to the ordered attribute since the auto generated functions are actually for NSSet not NSOrderedSet.Reiss
Everyone should file a duplicate bug with Apple on this. Duplicate bugs are one of the main ways the Apple engineers gauge priority for bug fixes.Antiphony
If I go to apple radar and write in problem id this: 10114310, it doesn't find the bug. IS it normal?Hattie
Update: today (May 11, 2012) the bug #10114310 is still open, 241 days after my report (September 13, 2011). Unbelievable.Laconic
@Dev: I've a similar experience with this one and just hit it again. You would wonder how it got past testing given that it simply does not work!Ivon
I just brought this up with an Apple engineer during one of the CoreData Lab sessions at WWDC. They acknowledge the issue and that it's a genuine bug, and from what I've seen it has the "critical" status, but of course there's no promise as to when they'll fix it. I don't think it'll be fixed in iOS6/Mountain Lion. I think it'd be good to duplicate this radar further. Currently it has about 25 dup's, the more the better!Kumler
For reference, here is my OpenRadar for the duplicate I entered: openradar.appspot.com/radar?id=1760411Kumler
I've also raised this on Apple's bug tracker. OpenRadar duplicate: openradar.appspot.com/radar?id=1850415Salic
Update: today (August 30, 2012) the bug #10114310 is still open. Almost a year has passed since my first report (September 13, 2011). Unbelievable.Laconic
Bug # 12476849 also filed on Radar for this issueQuarrelsome
Update: 442 days have passed since I filled the bug #10114310 and, guess what? Today (November 28, 2012) it is still open. I also believe that seems Apple doesn't want to solve the problem.Laconic
Simon, that's incredible. I use ordered relationships very often. Don't understand why apple don't fix it.Hattie
Yup - still exists in iOS 6.1 too. Filed radar 13191471 against not having a "known issue" about this in the docs.Kaneshakang
I've just filled a duplicate 13430690 just in case.Jeramie
After almost two years, Apple confirms this critical bug is still there: "This issue is still being investigated. If we have an update or need more information we will contact you." Wish they fix it in iOS 7.Saccharo
How this is not listed as a "Known issue" utterly confounds me. If you use the type-safe auto generated methods, you're gonna have a bad time (/me ends rant)Kaneshakang
I just had this same error and since I almost missed it, I wanted to point out @Sterling's github code below which beautifully fixes this problem. His repository is KCOrderedAccessorFix, and with one line of code, everything works as expected!Ninebark
Just checked today, it is still there in iOS 7 GM/OMG! I can't believe it…Saccharo
Update: 797 days, 2 new major iOS versions, and countless Xcode versions have passed since I filled the bug #10114310. And it is still "open." Unbelievable.Laconic
Does apple know about this bug ?? I just hit the same problem on iOS8 beta 5, Xcode 6 b6.Eun
Welcome to iOS 8 GM, little bug.Beebe
well actually one should use mutableOrderedSetValueForKey as mentioned here https://mcmap.net/q/92499/-exception-thrown-in-nsorderedset-generated-accessorsAurochs
I used a KCOrderedAccessorFix to fix this which broke with 64bit runtime. FML.Freshen
Hey guys, I'm still here with you, in iOS 9!Saccharo
Just verified it is still in iOS 10 beta 2. That's 5 years! I'm totally baffled.Saccharo
Update: after just 1,787 days from my bug report, today (August 1, 2016) Apple wrote me this: "Please verify this issue with the latest iOS 10 beta build and update your bug report at bugreport.apple.com with your results.". Let's hope this is the right time :)Laconic
Fixed in iOS 10! NSManagedObject dynamic accessor generation has fixed issues to properly generate accessors for ordered to-many relationships. developer.apple.com/library/prerelease/content/releasenotes/…Contingency
2019: iOS 12 I got this bug again.Flyspeck
A
243

I agree that there may be 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.

Arrest answered 27/10, 2011 at 22:3 Comment(18)
Your code snippet was just what I needed to get me around this issue. Hope Apple fixes the issue eventually, but so far I haven't seen any issues with using your approach.Ikeda
I am getting this error when I try to implement this work-around.. [__NSArrayI isEqualToSet:]: unrecognized selector sent to instance... This usually is from a item that has been release, but can't find where, anyone run into this?Harelip
@Harelip isEqualToSet is a method only NSSet has, so my guess is you've converted, created, or are treating a pointer as an NSArray before passing back to the NSManagedObject, which should if any reason be calling isEqualToOrderedSet to determine if the set needs to even change or be left as is.Arrest
@Leelll thanks for the reply, actually in mine I am using NSSet instead of NSOrderableSet set.. My code looks the same except I replaced NSMutableOrderedSet with NSMutableSet.. I wouldn't have though there was a difference, but maybe there is?Harelip
Also, it only happens if when I do [tempSet addObject:value].. If I comment this out and just do the creation of the Mutable with itself and then re-set it.. it works fine.. It doesn't error until after I re-set "property".Harelip
@Harelip I'm not sure what the underlying properties of a NSManagedObject are for a un-ordered to-many relationship. But what is not buggy is using the auto-generated method "addSubitemObject" don't implement this method, only declare it.Arrest
Just as a warning, I have implemented this code and it worked fine, however, later I found that its very slow! Be warned if you are repeatedly calling this in a loop.Hinze
As a follow up to my previous comment I have found a way to speed this up if you need to call addSubItemObject: repeatedly. See my answer below.Hinze
Similar to what this guy did: dev.totodotnet.net/post/16894288540/…Ianthe
@nicerobot - dead link now :(Hyohyoid
Wouldn't it be simpler to just use the mutable set accessor?Larena
Other reimplementations of Apple's broken methods, like the one here: https://mcmap.net/q/93702/-coredata-to-many-add-error and the one here: dev.totodotnet.net/post/16894288540/… call willChangeValueForKey: and didChangeValueForKey:, respectively before and after adding the object, but this solution does not. Should it? I've never used either of those methods so I don't know when they're usually triggered or what they're typically used for.Tangible
@MarkAmery the dynamic setter self.subitems should be sending the notifications. Have you tested if it is not, or only wanted to know they the methods were not called from addSubitemsObject?Arrest
@JLust I haven't tested anything, or looked too closely at the code or docs - I just noticed that other people's approaches to this explicitly called them, and that yours doesn't, and wondered what the reason for the difference was and whether it was a mistake on anyone's part.Tangible
@MarkAmery Tested. Verified. The dynamic setter self.subitems does send the notifications. So JLust solution is correct.Grasp
This is a good answer, but its inefficient. You copy the entire ordered set, modify and then copy it back. The effect is not just a hit to the ordered set, but notifications are sent out that that every time the ordered set is altered, its entire contents are changed! If this ordered set is used for a UITable for instance, this could have serious ramifications for updating. I've outlined in my solution exactly where the error comes from and I've shown a more efficient method for bypassing the error.Cerenkov
Hmmm, I tried this solution but the changes don't appear to be saved (after calling saveContext).Topcoat
Hmmm, my code is not exactly the same now but it works under iOS 7.1 in the simulator but doesn't work under iOS 8.1 in the simulator. :-(Topcoat
N
111

I've decided to improve the solution by implementing all the required methods:

static NSString *const kItemsKey = @"<#property#>";

- (void)insertObject:(<#Type#> *)value in<#Property#>AtIndex:(NSUInteger)idx {
    NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet insertObject:value atIndex:idx];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)removeObjectFrom<#Property#>AtIndex:(NSUInteger)idx {
    NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
    [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet removeObjectAtIndex:idx];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)insert<#Property#>:(NSArray *)values atIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet insertObjects:values atIndexes:indexes];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)remove<#Property#>AtIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet removeObjectsAtIndexes:indexes];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)replaceObjectIn<#Property#>AtIndex:(NSUInteger)idx withObject:(<#Type#> *)value {
    NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
    [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet replaceObjectAtIndex:idx withObject:value];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)replace<#Property#>AtIndexes:(NSIndexSet *)indexes with<#Property#>:(NSArray *)values {
    [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    [tmpOrderedSet replaceObjectsAtIndexes:indexes withObjects:values];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)add<#Property#>Object:(<#Type#> *)value {
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    NSUInteger idx = [tmpOrderedSet count];
    NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
    [tmpOrderedSet addObject:value];
    [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}

- (void)remove<#Property#>Object:(<#Type#> *)value {
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    NSUInteger idx = [tmpOrderedSet indexOfObject:value];
    if (idx != NSNotFound) {
        NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
        [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
        [tmpOrderedSet removeObject:value];
        [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
        [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
    }
}

- (void)add<#Property#>:(NSOrderedSet *)values {
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
    NSUInteger valuesCount = [values count];
    NSUInteger objectsCount = [tmpOrderedSet count];
    for (NSUInteger i = 0; i < valuesCount; ++i) {
        [indexes addIndex:(objectsCount + i)];
    }
    if (valuesCount > 0) {
        [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
        [tmpOrderedSet addObjectsFromArray:[values array]];
        [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
        [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
    }
}

- (void)remove<#Property#>:(NSOrderedSet *)values {
    NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
    NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
    for (id value in values) {
        NSUInteger idx = [tmpOrderedSet indexOfObject:value];
        if (idx != NSNotFound) {
            [indexes addIndex:idx];
        }
    }
    if ([indexes count] > 0) {
        [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
        [tmpOrderedSet removeObjectsAtIndexes:indexes];
        [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
        [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
    }
}
Nipple answered 4/3, 2012 at 16:42 Comment(12)
I'm using removeObjectFromSubitemsAtIndex but when I save the context, my program crashes. It seems the deleted object still exists. However if after removeObjectFromSubitemsAtIndex I call context deleteObject, when I save the context everything goes fine. Any idea? Thanks.Hattie
What is the crash type? 'removeObjectFromSubitemsAtIndex' doesn't delete these subitems, they still exist in your storage, it's just the way to remove the relationship between the objects.Nipple
what is kItemsKey? Is it the attribute name?Creatine
And I add these methods in my program and did some updates, for example I called insertObject. But nothing happened in my Core Data.Creatine
kItemsKey is a constant which was added just for convenience in KVO methods calls. It's a name of ordered relationship you are writing your methods for.Nipple
That's what I think it is. Thanks. But my problem is that the data is not saved to database by using these methods.Creatine
Did you call save: method of MOC you are using? Problem should not be in method I provided, as they are doing quite simple action, which definitely changes a state of NSManagedObject, most likely you are not calling save: or it returns with error.Nipple
Why are you making a copy of the mutableOrderedSet, and then reassigning the primitive to it? I thought the whole point of the mutableOrderedSet is that you can affect it directly, and it will handle key/value notifications and relationships for you. (see developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…)Jock
!!!!!!!!! Just copying the code and change the method names, it works perfectly!!! This is the fastest answer.Improbity
This is terrific, but creating the temporary copy of the ordered set is unnecessary. The culprit is willChangeValueForKey:withSetMutation:usingObjects, which you have successfully avoided. After that, just use [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values] or [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values] as appropriate. See my answer for details.Cerenkov
Awesome! I copied the codes and changed the variables a little. Works like a charm.Eastward
Great Answer!!! This should be the accepted answer with notes about the Apple bug. Tried a bunch of different versions from others that didn't work. This worked perfectly! Thank you!!!Merow
S
38

Yes, this is definitely a Core Data bug. I wrote up an ObjC-Runtime-based fix a while back, but at the time I figured it would be fixed soon. Anyway, no such luck, so I posted it up on GitHub as KCOrderedAccessorFix. Work around the problem on all your entities:

[managedObjectModel kc_generateOrderedSetAccessors];

One entity in particular:

[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];

Or just for one relationship:

[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];
Synchrocyclotron answered 22/1, 2013 at 7:5 Comment(5)
I wonder if this will conflict with the real fix from Apple or not?Cumshaw
This should not conflict with Apple's fix, as its purpose is to override Apple's implementation no matter what. When/if this is actually fixed by Apple, maybe I'll add a - (BOOL)kc_needsOrderedSetAccessorFix; or something that checks Foundation/iOS version.Synchrocyclotron
There is a KCOrderedAccessorFix.podspec already in the CocoaPods master repo. So in order to link this to your projects you can simply add "pod 'KCOrderedAccessorFix'" to your PodfileSeigniorage
This had some issues with iOS 8 (incorrect method signatures for objc_msg_send)Size
On iOS9 it works, nice job! This is the best solution ever, no need to change anything in your code!Bostwick
F
33

Instead to making a copy I suggest to use the accessor in NSObject to get access to the NSMutableOrderedSet of the relationships.

- (void)addSubitemsObject:(SubItem *)value {
      NSMutableOrderedSet* tempSet = [self mutableOrderedSetValueForKey:@"subitems"];
      [tempSet addObject:value];
 }

e.g. the Core Data Release Notes for iOS v5.0 refer to this.

In a short test it worked in my application.

Future answered 26/1, 2012 at 7:18 Comment(3)
Can't refactor literal strings as easily. The compiler can type check self.subitems if you use code.Sneakers
@Sneakers yes this is correct. It depends on the priority of the specific use case. In general I focus on save resources especially in this case because this was just a workaround.Future
The literal string may be replaced by NSStringFromSelector(@selector(subitems)) though :)Nativity
C
17

I've tracked the bug. It occurs in willChangeValueForKey:withSetMutation:usingObjects:.

This call sets off a chain of notifications which may be difficult to track, and of course changes to one responder may have implications for another, which I suspect is why Apple have done nothing.

However, it is okay in Set and its only the Set operations on an OrderedSet that malfunction. That means there are only four methods that need to be altered. Therefore, all I did was convert the Set operations to their equivalent Array operations. These work perfectly and minimal (but necessary) overheads.

On a critical level, this solution does suffer from one critical flaw; if you are adding objects and one of the objects already exists, then it is either not added or moved to the back of the ordered list (I don't know which). In either case, the expected ordered index of the object by the time we arrive at didChange is different from what was anticipated. This may break some people's apps, but it doesn't affect mine, since I am only ever adding new objects or I confirm their final locations before I add them.

- (void)addChildrenObject:(BAFinancialItem *)value {
    if ([self.children containsObject:value]) {
        return;
    }
    NSIndexSet * indexSet = [NSIndexSet indexSetWithIndex:self.children.count];
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
    [[self primitiveValueForKey:ChildrenKey] addObject:value];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
}

- (void)removeChildrenObject:(BAFinancialItem *)value {
    if (![self.children containsObject:value]) {
        return;
    }
    NSIndexSet * indexSet = [NSIndexSet indexSetWithIndex:[self.children indexOfObject:value]];
    [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
    [[self primitiveValueForKey:ChildrenKey] removeObject:value];
    [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
}

- (void)addChildren:(NSOrderedSet *)values {
    if ([values isSubsetOfOrderedSet:self.children]) {
        return;
    }
    NSIndexSet * indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.children.count, values.count)];
    [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
    [[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values];
    [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
}

- (void)removeChildren:(NSOrderedSet *)values {
    if (![self.children intersectsOrderedSet:values]) {
        return;
    }
    NSIndexSet * indexSet = [self.children indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
        return [values containsObject:obj];
    }];
    [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
    [[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values];
    [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
}

Of course, there is an easier solution. it is as follows;

- (void)addChildrenObject:(BAFinancialItem *)value {
    if ([self.children containsObject:value]) {
        return;
    }
    [self insertObject:value inChildrenAtIndex:self.children.count];
}

- (void)removeChildrenObject:(BAFinancialItem *)value {
    if (![self.children containsObject:value]) {
        return;
    }
    [self removeObjectFromChildrenAtIndex:[self.children indexOfObject:value]];
}

- (void)addChildren:(NSOrderedSet *)values {
    if ([values isSubsetOfOrderedSet:self.children]) {
        return;
    }
    [self insertChildren:values atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.children.count, values.count)]];
}

- (void)removeChildren:(NSOrderedSet *)values {
    if (![self.children intersectsOrderedSet:values]) {
        return;
    }
    [self removeChildrenAtIndexes:[self.children indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
        return [values containsObject:obj];
    }]];
}
Cerenkov answered 29/9, 2013 at 0:22 Comment(6)
Too bad everyone else seems to have overlooked this answer, definitely seems like the best solution.Cabinda
This solution has much better performance than the ones using orderedSetWithOrderedSet to make a local set. That has a big overhead when you have large data sets. The easier solution appears to just be a refactored version of the initial one with the methods not shown.Scrivner
I'm still seeing a crash in addChildren: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TrackHistory insertTrackpoints:atIndexes:]: unrecognized selector sent to instance 0x1702b1b20'Cyrie
@OwenGodfrey For the easier solution, where are you implementing these methods? I'm getting an exception: [Parent insertObject:inChildrenAtIndex:] unrecognized selector sent to instance 0x6180000ac480.Imagery
your variable is "Parent" with a capital "P"? Does that means you are call the the class "Parent", or do you have an instance variable named "Parent"? If my class is Parent, then i implemented this methods at the bottom of Parent, but you would need to call it on an instance, which would more likely be named "parent" with a lowercase "p", as these are not class methods.Cerenkov
I get the same error as Dalmazio Brisinda. It seems the category functions are not accessible from the main class? I have MyObject and MyObject+CoreDataProperties. I put all this code into MyObject (with the changes necessary (Children->MyObject) but I get the same exception. I'm guessing this must have to go into the Category in order to work but then each time I make a change to my Core Data Object this will get overwritten.Merow
A
11

The Apple docs To Many Relations says: you should access the proxy mutable set or ordered set using

NSMutableOrderedSet * set = [managedObject mutableOrderedSetValueForKey:@"toManyRelation"];

Modifying this set will add or remove relations to your managed object. Accessing the mutable ordered set using the accessor whether with [ ] or . notation is wrong and will fail.

Aurochs answered 31/10, 2014 at 13:42 Comment(2)
To be fair, the docs also say: "or one of the automatically-generated relationship mutator methods (see Dynamically-Generated Accessor Methods):"Bitch
Okay okay... you're right. Well then, let's say that that's the simplest working way...Aurochs
J
9

Received the same error, @LeeIII solution worked for me (thanks!). I suggest slightly modify it:

  • use objective-c category to store the new method (so we wont lose our method if Item is generated again)
  • check if we already have mutable set

Content of Item+category.m:

#import "Item+category.h"

@implementation Item (category)

- (void)addSubitemsObject:(SubItem *)value {
    if ([self.subitems isKindOfClass:[NSMutableOrderedSet class]]) {
        [(NSMutableOrderedSet *)self.subitems addObject:value];
    } else {
        NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems];
        [tempSet addObject:value];
        self.subitems = tempSet;
    }
}

@end
Judaea answered 3/3, 2012 at 17:51 Comment(1)
Good point to move this code into category. But still we need to embrace actual add/remove with will/setPrimitiveValue/didChange calls like in @Dmitry Makarenko answer.Terpsichorean
K
8

If you are using mogenerator, then instead of

[parentObject add<Child>sObject:childObject];

simply use:

[[parent object <child>sSet] addObject:childObject];
Kahlil answered 10/8, 2012 at 18:43 Comment(3)
Because mogenerator takes care of the extra code you would otherwise have to write and simply allows access to the underlying set object.Ietta
It looks like a fix has just been committed that means mogenerator will generate corrected bodies ... github.com/dmakarenko/mogenerator/commit/…Enroot
I am using mogenerator but I still have the bug.Fauver
C
7

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!

Circle answered 4/7, 2012 at 9:5 Comment(4)
This gives the following error: '[<CLASS 0x20886d10> valueForUndefinedKey:]: this class is not key value coding-compliant for the key subitems.'Quarrelsome
While it still irks me no end that this is not listed in Apple's known issues (I have opened a radar for the apparently futile gesture that it is), this solution has worked flawlessly for me.Kaneshakang
Wish I had seen this answer earlier; I was using the highest voted answer until I recently did some digging and finally implemented exactly what you have here :)Nativity
Why is addObject: called twice?Dicrotic
A
5

It seems that if you link the parent with the child by setting the parent to the child and not the other way around it works without crashing.

So if you do:

[child setParent:parent]

instead of

[parent setChildObects:child]

It should work, at least it works on iOS 7 and didn't had any problems with the relationship.

Address answered 1/2, 2014 at 21:3 Comment(1)
Doesn't do much good when both sides are to-many. Then there's no clear parent-child relationship.Vicky
I
3

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.

Indiscrete answered 12/9, 2011 at 8:59 Comment(1)
For CoreData to return or take an NSOrderedSet object, multiple conditions have to be met as this started question has shown. Most common mistakes I see when people sharing my code have been a developer not running Lion. NSOrderedSets framework is not available on snowleopard. But yes, I have not seen this fail, though I am not sure this is best on performance. I'd guess this takes the whole set and replaces it instead of just inserting the desired record.Arrest
C
3

I have had the same problem, but only when I tried something different to what I had been doing. I can't see the code for subItem, but I will assume that it has a reverse link to item. Lets call this reveres link, "parentItem", then the easiest solution is this:

Item *item = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:self.managedObjectContext];
item.name = @"FirstItem";

SubItem *subItem = [NSEntityDescription insertNewObjectForEntityForName:@"SubItem" inManagedObjectContext:self.managedObjectContext];

//[item addSubitemsObject:subItem];
subItem.parentItem = item;

The effect is that it makes use of apple's own code and it is simple and clean. In addition, the set is automatically added to, and all observers are updated. No problem.

Cerenkov answered 15/8, 2012 at 4:54 Comment(3)
This is very nice. It solves the whole problem and keeps it ordered aswell. Still insane that the bug is still present. Another benefit of this answer is that if you regenerate your core-data models you don't have to rewrite your bug fixes. Thanks!Alurta
See my other answer. I tracked the bug in more detail. This is still the easiest way, but the other method is the best because it opens up more possibilities.Cerenkov
Wow! Finally!!! Thanks! (Tried your other code, but got errors, something about that wrong type was sent at [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];)Decern
E
3

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.

Extension answered 16/10, 2013 at 10:38 Comment(0)
D
3

This issue occurred to me while migrating a project from Objective-C to Swift 2 with XCode 7. That project used to work, and for a good reason: I was using MOGenerator which had replacement methods to fix this bug. But not all methods require a replacement.

So here's the complete solution with an example class, relying on default accessors as much as possible.

Let's say we have a List with ordered Items

First a quick win if you have a one/to-many relationship, the easiest is to just do:

item.list = list

instead of

list.addItemsObject(item)

Now, if that's not an option, here's what you can do:

// Extension created from your DataModel by selecting it and
// clicking on "Editor > Create NSManagedObject subclass…"

extension List {
  @NSManaged var items: NSOrderedSet?
}

class List

  // Those two methods work out of the box for free, relying on
  // Core Data's KVC accessors, you just have to declare them
  // See release note 17583057 https://developer.apple.com/library/prerelease/tvos/releasenotes/DeveloperTools/RN-Xcode/Chapters/xc7_release_notes.html
  @NSManaged func removeItemsObject(item: Item)
  @NSManaged func removeItems(items: NSOrderedSet)

  // The following two methods usually work too, but not for NSOrderedSet
  // @NSManaged func addItemsObject(item: Item)
  // @NSManaged func addItems(items: NSOrderedSet)

  // So we'll replace them with theses

  // A mutable computed property
  var itemsSet: NSMutableOrderedSet {
    willAccessValueForKey("items")
    let result = mutableOrderedSetValueForKey("items")
    didAccessValueForKey("items")
    return result
  }

  func addItemsObject(value: Item) {
    itemsSet.addObject(value)
  }

  func addItems(value: NSOrderedSet) {
    itemsSet.unionOrderedSet(value)
  }
end

Of course, if you're using Objective-C, you can do the exact same thing since this is where I got the idea in the first place :)

Delacroix answered 11/11, 2015 at 10:31 Comment(0)
G
2

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
Generalship answered 22/8, 2014 at 16:2 Comment(0)
Z
2

I just got the problem in Swift (Xcode 6.1.1).

The answer was DO NOT CODE ANY METHOD OR ADDITIONAL THINGS in your NSManagedObject subclasses. I think it is a compilator mistake. Very strange bug ..

Hope it helps ..

Zymo answered 5/12, 2014 at 1:35 Comment(1)
So, if I cannot implement the other fixes, what should I do to fix this?Compander
D
2

I solved this problem by set the inverse to No Inverse, I don't know why, Maybe there is Apple Bug.enter image description here

Denishadenison answered 20/7, 2016 at 1:31 Comment(0)
Q
1

I have the same situation with an item called "signals" instead of "subitems". The solution with tempset works in my testing. Further, I had a problem with the removeSignals: method. This override seems to work:

- (void)removeSignals:(NSOrderedSet *)values {
    NSMutableOrderedSet* tempset = [NSMutableOrderedSet orderedSetWithOrderedSet:self.signals];
    for (Signal* aSignal in values) {
        [tempset removeObject:aSignal];
    }
    self.signals = tempset;
}

If there is a better way to do this, please let me know. My values input is never more than 10 -20 items so performance isn't much of a concern - nonetheless please point out anything relevant.

Thanks,

Damien

Quietly answered 9/2, 2012 at 17:37 Comment(0)
O
1

I found a fix for this bug that works for me. I just replace this:

[item addSubitemsObject:subItem];

with this:

item.subitemsObject = subItem;
Ozzy answered 13/5, 2013 at 13:34 Comment(0)
V
1

I found this question by googling for the error message, and just wanted to point out that I ran into this error in a slightly different way (not using ordered sets). This isn't quite an answer to the given question, but I'm posting it here just in case it is helpful to anyone else who stumbles across this question while searching.

I was adding a new model version, and added some relationships to existing models, and defined the add*Object methods in the header file myself. When I tried to call them, I got the error above.

After reviewing my models, I realized I had stupidly forgotten to check the "To-Many Relationship" checkbox.

So if you're running into this and you're not using ordered sets, double check your model.

Voigt answered 17/5, 2013 at 13:39 Comment(0)
S
1

Better version of the correct answer in SWIFT

var tempSet = NSMutableOrderedSet()
if parent!.subItems != nil {
    tempSet = NSMutableOrderedSet(orderedSet: parent!.subItems!)
}

tempSet.add(newItem)
parent!.subItems = tempSet
Schaefer answered 14/1, 2018 at 10:40 Comment(0)
H
0

I found using the method by LeeIII worked, but on profiling found it was drastically slow. It took 15 seconds to parse 1000 items. Commenting out the code to add the relationship turned 15 seconds into 2 seconds.

My workaround (which is faster but much more ugly) involves creating a temporary mutable array then copying into the ordered set when all the parsing is done. (this is only a performance win if you are going to add many relationships).

@property (nonatomic, retain) NSMutableArray* tempItems;
 ....
@synthesize tempItems = _tempItems;
 ....

- (void) addItemsObject:(KDItem *)value 
{
    if (!_tempItems) {
        self.tempItems = [NSMutableArray arrayWithCapacity:500];
    }
    [_tempItems addObject:value];
}

// Call this when you have added all the relationships
- (void) commitRelationships 
{
    if (_tempItems) {
        self.items = [NSOrderedSet orderedSetWithArray:self.tempItems];
        self.tempItems = nil;
    }
}

I hope this help someone else!

Hinze answered 30/4, 2012 at 8:55 Comment(0)
I
0

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 ;)

Inexperience answered 13/5, 2012 at 3:39 Comment(0)
S
0

I'm quite sure it is finally fixed in iOS 10 beta 6!

Saccharo answered 15/8, 2016 at 19:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.