In my app, I have a NSDictionary whose keys should be instances of a subclass of NSManagedObject.
The problem, however, is that NSManagedObject does not implement the NSCopying protocol which means that no Core Data objects / instances of NSManagedObject can be used as dictionary keys even though the -[hash] method works fine for them.
Was should I do?
There are four options:
Use a different object as the dictionary key instead, and lookup from that. [object objectID] or +[NSValue valueWithNonretainedObject:] seem the most obvious
Use CFDictionaryCreateMutable() to create a dictionary with retained keys, rather than copied, instead, and then call CFDictionarySetValue() to store the objects
On OS X or iOS6+, [NSMapTable mapTableWithStrongToStrongObjects] gives you a purely Objective-C equivalent to CFMutableDictionary
Implement NSCopying for your managed object subclass, such that it returns self (with a bumped reference count if you're not using ARC)
Notes
+valueWithNonretainedObject: is pretty dangerous, since it's possible to be left with a dangling pointer; likely best to avoid.
Storing object IDs is fine, apart from the fact that new objects start out life with a temporary ID. That ID then changes to a permanent one when the context is saved to disk (or -obtainPermanentIDsForObjects:… is called). Your mapping code needs to be smart enough to handle this unless it can guarantee that all incoming objects already have a permanent ID.
Implementing NSCopying like this feels a bit icky, but should work just fine. As it happens, this is exactly the approach NSURLSessionTask takes, I presume for dictionary friendliness.
Prior to OS X 10.8 Mountain Lion, it used to be possible to create a regular NSMutableDictionary and then call CFDictionarySetValue() for it. That's no longer the case though; new dictionaries now have proper copy callbacks specified down at the CF level, rather than purely being a feature of NSMutableDictionary.
I suggest to use [[[myManagedObject objectID] URIRepresentation] absoluteString] as your key.
Could you create a wrapper class, that contains a reference to the instance of NSManagedObject that you want to use as a dictionary key? You could then make this wrapper class implement NSCopying, along with a hash method (perhaps just calling the NSManagedObject's hash method), and use this wrapper as the dictionary key.
I had a similar problem, in which I needed to bundle several entities with additional data for each, and initially tried:
#{entity1:data1, #entity2:data2, #entity3:data3}
this didn't work for the reason above (NSCopying), so I did:
#[
#{#"entity":entity1, #"data":data1},
#{#"entity":entity2, #"data":data2},
#{#"entity":entity3, #"data":data3}
]
But this solution makes sense only if you don't need dictionary style access to these entities or are happy to iterate to find what you need. In my case this was a packaging problem. Note that if you pass these entities around the NSManagedObjectContext need to be the same to use them.
Related
Say I'd like a mutable, unordered to-many relationship. For internal optimization reasons, it'd be best to store this in an NSMutableDictionary rather than an NSMutableSet. But I'd like to keep that implementation detail private.
I'd also like to provide some KVO-compliant accessors, so:
- (NSSet*)things;
- (NSUInteger)countOfThings;
- (void)addThings:(NSSet*)someThings;
- (void)removeThings:(NSSet*)someThings;
Now, it'd be convenient and less evil to provide accessors (private ones, of course, in my implementation file) for the dictionary as well, so:
#interface MYClassWithThings ()
#property (retain) NSMutableDictionary* keyedThings;
#end
This seems good to me! I can use accessors to mess with my keyedThings within the class, but other objects think they're dealing with a mutable, unordered (, unkeyed!) to-many relationship.
I'm concerned that several things I'm doing may be "evil" though, according to good style and Apple approval and whatnot. Have I done anything evil here? (For example, is it wrong not to provide setThings, since the things property is supposedly mutable?)
I wouldn't make a property (even a private one) for the dictionary, but I don't think there's anything wrong with it.
… is it wrong not to provide setThings, since the things property is supposedly mutable?
Yes. KVC will not like the absence of a setThings: method.
Nothing evil here. The only mandatory mutation methods for an unordered relationship are addThings: and removeThings: (see the KVC doc). The accessors for your keyedThings property won't collide with any KVC accessor, so you're also fine there.
To put your mind at ease, the only things Apple's static analyzer is known to check for are messages to undocumented APIs. Other than that, if your implementation decisions don't affect the app's behavior, you're alright for App Store approval.
Update: I got interested in this question and re-read the KVC doc for myself. The language here gave me pause:
To-many unordered relationships are
most often modeled using instance of
NSSet or a subclass. In that case the
key-value coding will, if it doesn’t
find these accessor patterns for the
property, directly access the set.
Typically, you only implement these
methods if you are using a custom
collection class that needs to be
accessed as if it was a set.
It sounds like the author would prefer that you get rid of things and implement enumeratorOfThings: and memberOfThings:.
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.
What do I need to do to update a tableView bound to an NSArrayController when a method is called that updates the underlying array? An example might clarify this.
When my application launches, it creates a SubwayTrain. When SubwayTrain is initialised, it creates a single SubwayCar. SubwayCar has a mutable array 'passengers'. When a Subway car is initialised, the passengers array is created, and a couple of People objects are put in (let's say a person with name "ticket collector" and another, named "homeless guy"). These guys are always on the SubwayCar so I create them at initialisation and add them to the passengers array.
During the life of the application people board the car. 'addPassenger' is called on the SubwayCar, with the person passed in as an argument.
I have an NSArrayController bound to subwayTrain.subwayCar.passengers, and at launch my ticket collector and homeless guy show up fine. But when I use [subwayCar addPassenger:], the tableView doesn't update. I have confirmed that the passenger is definitely added to the array, but nothing gets updated in the gui.
What am I likely to be doing wrong? My instinct is that it's KVO related - the array controller doesn't know to update when addPassenger is called (even though addPassenger calls [passengers addObject:]. What could I be getting wrong here - I can post code if it helps.
Thanks to anyone willing to help out.
UPDATE
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it? It seems awfully cumbersome to copy the array, release the old one, and update the copy (as opposed to just adding to the existing array).
I don't know if its considered a bug, but addObject: (and removeObject:atIndex:) don't generate KVO notifications, which is why the array controller/table view isn't getting updated. To be KVO-compliant, use mutableArrayValueForKey:
Example:
[[self mutableArrayValueForKey:#"seatedPlayers"] addObject:person];
You'll also want to implement insertObject:inSeatedPlayersAtIndex: since the default KVO methods are really slow (they create a whole new array, add the object to that array, and set the original array to the new array -- very inefficient)
- (void)insertObject:(id)object inSeatedPlayerAtIndex:(int)index
{
[seatedPlayers insertObject:object atIndex:index];
}
Note that this method will also be called when the array controller adds objects, so its also a nice hook for thinks like registering an undo operation, etc.
I haven't tried this, so I cannot say it works, but wouldn't you get KVO notifications by calling
insertObject:atArrangedObjectIndex:
on the ArrayController?
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it?
First off, it's setSeatedPlayers:, with the colon. That's vitally important in Objective-C.
Using your own setters is the correct way to do it, but you're using the incorrect correct way. It works, but you're still writing more code than you need to.
What you should do is implement set accessors, such as addSeatedPlayersObject:. Then, send yourself that message. This makes adding people a short one-liner:
[self addSeatedPlayersObject:person];
And as long as you follow the KVC-compliant accessor formats, you will get KVO notifications for free, just as you do with setSeatedPlayers:.
The advantages of this over setSeatedPlayers: are:
Your code to mutate the set will be shorter.
Because it's shorter, it will be cleaner.
Using specific set-mutation accessors provides the possibility of specific set-mutation KVO notifications, instead of general the-whole-dang-set-changed notifications.
I also prefer this solution over mutableSetValueForKey:, both for brevity and because it's so easy to misspell the key in that string literal. (Uli Kusterer has a macro to cause a warning when that happens, which is useful when you really do need to talk to KVC or KVO itself.)
The key to the magic of Key Value Observing is in Key Value Compliance. You initially were using a method name addObject: which is only associated with the "unordered accessor pattern" and your property was an indexed property (NSMutableArray). When you changed your property to an unordered property (NSMutableSet) it worked. Consider NSArray or NSMutableArray to be indexed properties and NSSet or NSMutableSet to be unordered properties. You really have to read this section carefully to know what is required to make the magic happen... Key-Value-Compliance. There are some 'Required' methods for the different categories even if you don't plan to use them.
Use willChangeValueForKey: and didChangeValueForKey: wrapped around a change of a member when the change does not appear to cause a KVO notification. This comes in handy when you are directly changing an instance variable.
Use willChangeValueForKey:withSetMutation:usingObjects: and didChangeValueForKey:withSetMutation:usingObjects: wrapped around a change of contents of a collection when the change does not appear to cause a KVO notification.
Use [seatedPlayers setByAddingObject:sp] to make things shorter and to avoid needlessly allocating mutable set.
Overall, I'd do either this:
[self willChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
[seatedPlayers addObject:sp];
[self didChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
or this:
[self setSeatedPlayers:[seatedPlayers setByAddingObject:sp]];
with the latter alternative causing an automatic invocation of the functions listed under 1. First alternative should be better performing.
All objects used as keys in NS(Mutable)Dictionaries must support the NSCopying protocol, and those objects are copied when they're used in the dictionary.
I frequently want to use heavier weight objects as keys, simply to map one object to another. What I really mean when I do that is effectively:
[dictionary setObject:someObject forKey:[NSValue valueWithPointer:keyObject]];
("When I come back and hand you this same key object instance again, get me that same value out.")
...which is exactly what I end up doing to get around this design sometimes. (Yes, I know about NSMapTable in desktop Cocoa; but e.g. iPhone doesn't support this.)
But what I don't really get is why copying the key is necessary or desirable in the first place. What does it buy the implementation or caller?
The copy ensures that the values used as keys don't change "underhand" while being used as keys. Consider the example of a mutable string:
NSMutableString* key = ...
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject: ... forKey: key];
Let's assume that the dictionary did not copy the key, but instead just retained it. If now, at some later point, the original string is modified, then it is very likely that you are not going to find your stored value in the dictionary again even if you use the very same key object (i.e., the one key points to in the example above).
In order to protect yourself against such a mistake, the dictionary copies all keys.
Note, by the way, that it is simple enough to define -copyWithZone: as just doing return [self retain]. This is allowed and good code if your object is immutable, and the NSCopying contract is specifically designed such that the object returned has to be (sorta, kinda) immutable:
Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable.
(from NSCopying Reference)
and
The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class.
(from -copyWithZone: Reference)
Even if your objects are not immutable, you might get away with that implementation if you only ever use identity-based equality/hash implementations, i.e., implementations which are not affected in any way by the object's internal state.
If you want to store pointers as keys then you'll need to wrap them in a NSValue object with +valueWithPointer:.
Since iOS 6 if you want to use pointers as keys, you can use the NSMapTable object, see http://nshipster.com/nshashtable-and-nsmaptable/
You can specify whether keys and/or values are stongly or weakly held:
NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory
valueOptions:NSMapTableWeakMemory];
Another option that could be appropriate sometimes is to use NSCache, which holds keys strongly and is actually thread-safe.
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.