NSInvalidArgumentException with NSOrderedSet using CoreData

June 19, 2012

If you have ever worked on an iOS 5 application and came across a strange issue adding objects to a NSOrderedSet that is part of a NSManagedObject, you are not alone. This bug was reported on Open Radar on September 13, 2011, and was still reproducible in the latest versions of iOS5.

Setting up the Problem

To replicate the problem, create two NSManaged objects.

Create a NSManagedObject called “Student”. Add the attribute “name” of type string.

Create a NSManagedObject called “Lecture”. Add the attribute “name” of type string.

On your Student object, add the relationship “lectures”, and set the Destination to “Lecture”. Make sure the boxes are checked next to “To-Many relationship” and “Ordered”.

On your Lecture object, add the relationship “students”, and set the type to “Student”. Set the Inverse to “lectures”. Make sure the boxes are checked next to “To-Many relationship” and “Ordered”.

A student can have many lectures, and a lecture may have many students. We set the inverse relationship because we might need to know which students are in a particular lecture, as well as which lectures a particular student is enrolled in.

Replicating the Problem

To replicate this problem, create a Lecture and save it to CoreData. Next, create a student, and try to insert it into the “students” relationship in the lecture object you just created:

What happens when you run this?

When you run this code, assuming you set the inverse relationship, you should get this exception:

‘NSInvalidArgumentException’, reason: ‘*** -[NSSet intersectsSet:]: set argument is not an NSSet’

How can I fix this?

One clean approach to fixing this issue is to add a category to your lectures model:

Note: one reason you should use a category – if you decide to make changes to your CoreData model and regenerate the class, it will not delete your category. If you placed this function in your model, re-generating the model will erase your code.

Select New>File.

In the left pane, select “Cocoa Touch”, and then select “Objective-C category”.

In the “Category on” box, select “Lecture”, and name the Category “methods” (it can be named whatever you like, but I am sticking with “methods” for this small example).

You will notice in your project explorer, a file will be created called “Lecture+methods.h”. This file will contain all of your extra methods for your object.

-(void)addStudentsObject:(Student *)value
{
// Create a mutable set with the existing objects, add the new object, and set the relationship equal to this new mutable ordered set
NSMutableOrderedSet *students = [[NSMutableOrderedSet alloc] initWithOrderedSet:self.students];
[students addObject:value];
self.students = students;
}

That’s it! Running the code should now work.

Jon Nalewajek

Not only is Jon largely responsible for mobile application development at Cypress North, but he also plays a role in developing custom ASP.NET and PHP solutions. Whether it is creating web services, custom web components for your website, or helping you build your next big mobile app, Jon has you covered.

Share this post

4 comments

I tried numerous variants of this workaround. They appear to work while the application is in-memory. However, after I saved the application context to the Core Data data store, I noticed that the ordered one-to-many relationship isn’t reflected in the .sqlite file. I confirmed this by restoring the object model after re-launching the application.

In summary, the workaround fixes the object model in memory, but doesn’t save it correctly to the data store. The next time you attempt to re-create the object model, you will find the ordered one-to-many relationships missing.

We had not noticed this issue because we were refreshing our cached data each time the application loaded by making a call to the server. This reloaded all of the entities while the application was in-memory, which, as you stated, worked correctly.

I just mocked up a demo application to test what you noticed, and saw the problem. I stepped through the debugger to see what was going on, and found a simple fix.

I noticed that the following if statement always evaluates to true:

if ([self.students isKindOfClass:[NSMutableOrderedSet class]])

Because of this, the following line always gets called:
[(NSMutableOrderedSet *)self.students addObject:value];

Now, you would think this should work. If self.students is a mutable array, we should simply be able to add a student object to that array, and commit this change to CoreData. However, this is not the case. I have not had time yet to figure out exactly what is going on, but as you noted, the changes are not being committed to CoreData.

If we comment out everything except what is in the else-statement, things should work appropriately.