Ok guys, here is my question.
I would use CoreData + Source List + NSTreeController to create something similar to the source list of Finder (or iTunes if you prefer).
My problem is that what I have in my source list doesn't come entirely from CoreData. For example I have a section (root) called "data from core data" that is populated using CoreData, but I also have some "fake" sections (i.e. "Favorites" section) that are created run-time.
For this reason I think that binding the OutlineView and the NSTreeController directly to CoreData using entities is not feasible and that I have to use something intermediate like a NSMutableArray that I populate at start with data coming from CoreData and the "fake" sections using ad-hoc classes for the members (like in the "SourceView" example by Apple). This is the first question: (1) Am I right in doing this?
My second question is about this NSMutableArray. Let's say that using this array could be a solution, I was wondering which is the correct way to fill the array in. In the example "SourceView" the intermediate array is populated run-time using the NSTreeController (insertObject:), but another solution I was thinking of is subclassing NSMutableArray and change directly the content of the array. (2) Am I breaking the MVC in this way?
One approach to mix persistent nodes with temporary nodes which only exist at runtime, is to add a NSInMemoryStoreType store to your persistentStoreCoordinator. Here is a tutorial on how to do this: http://simplyhacking.com/source-list-with-core-data.html
Related
I have a class which as its properties has a number of NSSet values. The content of these sets is not stored in Core Data, but comes from another source. They are basically ID strings which are unique. No point in filling up Core Data with them as separate entities.
However, as far as I can see it's not possible to store an NSSet as an attribute of an NSManagedObject. I guess I would need to serialise it into binary data by hand, whenever the object gets stored, and deserialise it when it gets retrieved from the persistent store? The same would also apply to storing other collection classes.
Has anybody else ever come across this issue and is able to give some advice?
You can set the attribute type to 'transformable' and then directly store the set into the attribute. Be sure that all of the contents of the set conform to < NSCoding >.
In Xcode, in the Project Navigator open up the project_name.xcdatamodeld source for the model editor and select the entity in question. Choose Editor in the Xcode menu bar and there is a Create NSManagedObject subclass... item. Make the subclass and use it for extra non-Core Data properties. The subclass becomes part of the model in place of the entity. It retains all of the entity's behaviors.
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.
I'd like to have an NSArray or NSMutableArray that always shows a filtered view of a data set. That is, if the filter is set to "show me things with the letter a", and an item "blah" is added, that item would automatically show up in the list. However, if "bluh" were added, it would not show up in the filtered list (but would still really be there in the underlying dataset).
I see that there are filter methods on NSArray and NSMutableArray, but these are one shot methods. That is, the filter occurs when you call the method and never again (unless of course you call the filter method again).
I'm coming from the Java world, were I used Glazed Lists extensively for this kind of thing. I was hoping for a similar solution baked into Cocoa.
You'll most likely want to use NSArrayController as suggested by Ole.
You can use setFilterPredicate: on it as suggested, and then you'll want to access the objects by calling arrangedObjects on the controller. You don't need setAutomaticallyRearrangesObjects: unless you're intending to have your data sorted (using sort descriptors — NSSortDescriptor instances).
NSArrayController is really set up to handle displaying things in a table view very easily, so if that's your end goal, then this is exactly what you want. It integrates nicely with NSSearchField to handle predicates in the UI.
If you're using this as some back end object that's getting passed around, then you might want to write something yourself that's a little less heavy-weight than NSArrayController.
Coming from the iPhone, I'm not very familiar with NSArrayController but you might want to take a look at it. It seems to me that setFilterPredicate: in combination with setAutomaticallyRearrangesObjects:YES might do the trick.
Other than that, it should be quite easy to roll your own solution using Key-Value Observing. Start with a mutable copy of the array you want to filter and filter it with filterUsingPredicate: as you noted above, then register yourself as an observer for insertions and deletions in the original array and when your observer method gets called, call evaluateWithObject: on the newly inserted objects to decide whether to insert them into your filtered array.
I'm trying to make a Core Data app in which when you select one "Player" in a TableView, and a list of all teammates appears in a second tableView, with a column for how many times those two players have played on the same "Team" (another entity).
This has got me completely stuck, because while I know how to fill up a table from a normal array, using ArrayControllers and Core Data has really cluttered up my view of the situation.
How would you approach this?
Yours is a Bindings problem, not a Core Data problem. :-)
You should definitely get a handle on Cocoa Bindings before dealing with Core Data. This is stated in the docs and is very true.
The subject of your question seems to differ from the body, so I'll answer both.
Showing the Teammates
Core Data aside, assume you have a table representing Player instances. Player has one Team. Team has many players. Therefore, it's inferred that an instance of Player has "team.players" (minus itself) as teammates. Whether you're using Core Data to manage the model or not, this is true of the overall relationships.
If you read through and master Cocoa Bindings, you'll find that this is not hard at all to set up using a basic Master/Detail setup (with an extra array controller for the Detail part, for simplicity). Your Master array controller represents all Player instances, while your detail array controller represents the Teammates - or the Master's selection's "team.players" (minus itself).
The Teammates array controller will have its entity and managed object context set up as usual (see the docs). The "contentSet" will be bound to the Master array controller's "selection" controller key, with "team.players" as the model key path.
The trick is to filter out the Master controller's selected player using predicates. This you can do with the array controller's Filter Predicate. Maybe one with a format of "self != %#", where "%#" represents the Master array controller's selection. I'll leave Predicates (a complicated topic unto itself) to you. Remember, you can set them in code ([myController setFilterPredicate:myPredicate]) or by using bindings. Predicates are independent of Core Data as well.
Getting Selection
Since the array controller is in charge of the array the table represents, it's best to ask the array controller what its selection is. One way is to ask its -arrangedObjets for the objects at its -selectedIndexes.
NSArray * selectedObjects = [[myArrayController arrangedObjects] objectsAtIndexes:[myArrayController selectedIndexes]];
You can also ask it for its -selectedObjects. There are differences between these two approaches that are described by the documentation (API reference and conceptual docs) that you should definitely understand, but asking the controller is the most important concept, regardless of whether you use an NSArrayController or some custom controller that conforms to the and protocols.
Disclaimer: Typed hastily after a social Sake evening. Not checked for errors. :-)
I'm trying to figure out how to decide when to use NSDictionary or NSCoder/NSCoding?
It seems that for general property lists and such that NSDictionary is the easy way to go that generates XML files that are easily editable outside of the application.
When dealing with custom classes that holds data or possibly other custom classes nested inside, it seems like NSCoder/NSCoding would be the better route since it will step through all the contained object classes and encode them as well when an archive command is used.
NSDictionary seems like it would take more work to get all the properties or data characteristics to a single level to be able to save it, where as NSCoder/NSCoding would automatically encode nested custom classes that implement the NSCoding interface.
Outside of it being binary data and not editable outside of your application is there a real reason to use one over the other? And along those lines is there an indicator of which way you should lean between the two? Am I missing something obvious?
Apple's documentation on object graphs has this to say:
Mac OS X serializations store a simple hierarchy of value objects, such as dictionaries, arrays, strings, and binary data. The serialization only preserves the values of the objects and their position in the hierarchy. Multiple references to the same value object might result in multiple objects when deserialized. The mutability of the objects is not maintained.
…
Mac OS X archives store an arbitrarily complex object graph. The archive preserves the identity of every object in the graph and all the relationships it has with all the other objects in the graph. When unarchived, the rebuilt object graph should, with few exceptions, be an exact copy of the original object graph.
The way I interpret this is that, if you want to store simple values, serialization (using an NSDictionary, for example) is a fine way to go. If you want to store an object graph of arbitrary types, with uniqueness and mutability preserved, using archives (with NSCoder, for example) is your best bet.
You may also want to read Apple's Archives and Serializations Programming Guide for Cocoa, of which the aforelinked page on object graphs is a part, as it covers this topic well.
I am NOT a big fan of using NSCoding/NSCoder/NSArchiver (we need to pick a name!) to serialise an object graph to a file.
Archives created in this way are incredibly fragile. If you save an object of class Foo then by golly you need to make sure when you load the data back in you have a class Foo in your application.
This makes NSCoder based serialisation difficult from the perspective of sharing files with other applications or even forwards compatibility with your future application.
I forgot to list what I would recommend.
NSCoding can be ok in certain situations: if you're just doing something quick and simple (although you do have to write a lot of code - two methods per class to be serialised). It can also be ok if you're not worried about compatibility with other applications.
Export/import via property lists (perhaps using the NSPropertyListSerializaion class) is a fine solution. XML based plists are easy to create and edit. Main advantage to plists is that you're not tying the file format to just your application.
You can also create your own XML based file format and read/write to it using NSXMLDocument API and friends. This really isn't much more work than using property lists.
I think you're a bit confused, NSDictionary is a data structure, it also happens to implement the NSCoding protocol. So in essence, you could either put all your data into a NSDictionary and have that encode itself later on, or you can implement the NSCoding protocol and encode your object tree using the NSCoder API. Based on the type of NSCoder object passed in to the encodeWithCoder: method, is the output of your encoding.