All those “NSOrderedSet was added later and thus doesn’t have to play nice with other components” bugs drive me crazy…
(https://twitter.com/kubanekl/status/413447039855640576)
I have two managed objects and an ordered 1:N relationship between them, which is backed by an instance (or more precise a subclass) of NSOrderedSet
. I want to manage this relationship using a NSArrayController
in order to profit from features it offers (selection management, binding of the content, bindings to views like NSTableView
).
Since NSOrderedSet
is not a subclass of NSSet
, the contentSet
binding of NSArrayController
doesn't work with that relationship. I found following thread and tried to implement the suggestions mentioned there.
The first suggestion is to use the contentArray
binding and a value transformer for transforming the ordered set to an array on the fly. The problem with this solution is the reassigning of the content each time a change is made, which is not what I want.
The second suggestion provided in the mentioned thread is to use the contentArray
binding and apply the @array
operator to the model key path. I've tried that, but the underlying relationship was not touched at all when adding/removing objects through the NSArrayController
.
Another option I found is using sort descriptors with the contentSet
binding. This would require making the relation unordered in order to make the contentSet
binding work and introducing a new attribute used especially for managing the order. This would furthermore require a custom ordering mechanism to implement and it would mess up the model. Honestly said, I would like to avoid this solution.
My question is pretty clear: Is there any way to manage an ordered Core Data relationship using NSArrayController
? If so, which is the best way causing as little pain as possible?
It is indeed very sad that NSArrayController does not come with support for ordered relationships. As a keen observer to the bindings technology I find it sub optimal that it seems that Apple has "abandoned" it without saying anything. The last notable change that Apple introduced with regards to bindings are NSTreeController bug fixes. That was I believe with 10.6/10.7. It seems that Apple does not want to touch the bindings technology anymore. I am not sure why because bindings are really great sometimes. They can be the "90% solution". During prototyping this is fine. I am using bindings where it makes sense and having NSArrayController with ordered relationships support would be something great.
Most of the solutions that have been mentioned already are no real solutions. But this depends. Here is something to think about:
- If you are planning to support iCloud then you should not/cannot use ordered relationships anyway because Core Data on iCloud does not support them.
- Since ordered relationships are fairly new and the desire for a ordered collection of objects existed long before them, there must be a way in Core Data to mimic ordered relationships. You have already pointed out what 99.9% of the Core Data eating world did before ordered relationships were available: Sort by an additional attribute. You have pointed out that this is messing up the model but I disagree: It is true that you have to add an additional attribute to your model which does not necessarily "represent" true model data. But how many ordered relationships are you planning to have in your model? Usually you don't have that many per application. Even though it feels a bit dirty this is what has been done by lots of people for at least three major releases of Core Data (10.4, 10.5 and 10.6). Even today this solution is used for backwards compatibility or if you want to use iCloud. It is a "pragmatic" solution. Not a nice one but pragmatic. Also please not: Even if you were using ordered relationships the order of your objects has to be stored somewhere. If you are using the SQLite store then having an ordered relationship causes the NSSQLiteStore to create an additional column for you. The column has the name: Z_FOK_$RELATIONSHIPNAME. So by using ordered relationships you are merely doing something that is done for you under the hood anyways. This means that it does not really matter from a pure technical perspective if you are using ordered relationships or an additional attribute. The underlying technical problems remain the same. Ordered relationships are no magic.
- If you are planning to go with the "additional attribute" solution be aware that you have to adjust the value of this attribute a lot: Every time the user is changing the ordere via drag and drop you have to modify the value of the attribute. This seems wasteful but it really isn't. Even the worse case: A user who is exchanging the object at row 0 with an object at the last possible row does only cause 2 attribute changes. The complexity of the trivial solution for changes needed to represent any change that can be made by drag and drop in a table view is O(n) where n is the number of selected rows. This is really not that bad since users are usually not reordering 10000000 rows at once and even then there are smarter algorithms out there which are not that hard to implement.
- If you are looking the for cleanest solution you should subclass NSArrayController and add a "orderedContentSet" bindings yourself. You can find out how to do that by reading the Cocoa Bindings Programming topic guide. The guide contains an example: https://developer.apple.com/library/mac/documentation/cocoa/conceptual/CocoaBindings/Concepts/HowDoBindingsWork.html (Listing 2). The bad thing about this is that you are subclassing NSArrayController which is usually a no go. Many people tend to subclass NSArrayController for reasons that don't justify subclassing it. In this case however subclassing NSArrayController is justified if you want to go with the cleanest solution.
- For 3. there are generic solutions out there which do a lot of the stuff for you. Don't use them.