Back to Blog

NSInvalidArgumentException with NSOrderedSet using CoreData

June 19, 2012

By Jon Nalewajek
4 Comments

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.

  1. Create a NSManagedObject called “Student”. Add the attribute “name” of type string.
  2. Create a NSManagedObject called “Lecture”. Add the attribute “name” of type string.
  3. 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”.
  4. 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:

AppDelegate *delegate = [[UIApplication sharedApplication] delegate];

// Create the lecture
Lecture *lecture = [NSEntityDescription insertNewObjectForEntityForName:@"Lecture" inManagedObjectContext:delegate.managedObjectContext];
lecture.name = @"Obedience Training";

// Create the student
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:delegate.managedObjectContext];
student.name = @"Rogan";

[lecture addStudentsObject:student];
[delegate saveContext];

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.

  1. Select New>File.
  2. In the left pane, select “Cocoa Touch”, and then select “Objective-C category”.
  3. 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.

In your Lecture+methods.h file, override the addStudentsObject: method –

-(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

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.

See Jon's Most Recent Posts

Share this post

4 comments

Julian Asamer September 25, 2012Reply

Hi, your solution code looks fine except that you forget to actually add the student in the else-case.

Cheers!

Jonathan Nalewajek September 26, 2012Reply

Julian,

Thank you very much for the correction. I was writing this by scratch (instead of copying and pasting from xCode) and missed that line.

I updated the post to reflect this change.

Cheers!
Jon

Yuen Boon Yee October 17, 2012Reply

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.

Jonathan Nalewajek October 17, 2012Reply

Yuen,

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.

Comment out everything except:

NSMutableOrderedSet *students = [[NSMutableOrderedSet alloc] initWithOrderedSet:self.attendees];
[students addObject:value];
self.students = students;

I will update the post to reflect this change. Thank you for bringing it to my attention!

Best,
Jon

Leave a Reply

Search our blog

Start A Project

Categories

What's next?

Well...you might like one of these

Article

Is Google Analytics 4 a Tactical Move...

There’s something fishy going on with the way that Google...

Read article

Article

How We Build It: Website Process with...

This month, I sat down with Matt Mombrea, Chief Technical...

Read article

Article

[SOLVED] Using an in-memory repository....

.NET Core Data Protection One of the main benefits of...

Read article