How to sort an NSDictionary on iPhone

(because I googled it, and on the first page of hits I couldn’t find any copy/pasteable source for this common problem, here’s an answer with (poor) public domain source code)

In Objective-C / Cocoa, you cannot sort a dictionary (which is what I’m about to fix…). You cannot place any kind of persistent ordering onto a dictionary.

(this is a common requirement in modern software, such that you expect any standard lib to contain a core class to handle it. For instance, Java’s LinkedHashMap…)

I would prefer a standard-lib solution, especially since it’ll have all the features, and probably no bugs – and it’s more likely to be optimized for performance. But, since Apple hasn’t given us one, here’s the lazy-and-quick way to do it:

  1. Make a class that includes an Array and a Dictionary
  2. copy/paste the method signatures from NSDictionary and NSMutableDictionary into your new class
  3. implement each method with a call to your embedded dictionary
  4. EXCEPT for the methods:
    • allKeys
    • setObject:forKey:
    • setValue:forKey:
    • removeAllObjects
    • removeObjectForKey:
    • …[and all the methods to do with enumeration and init* – but I don’t have time to do all those, too much hassle]…
  5. …for those methods, update the embedded Array as well, and for “allKeys” specifically: return a copy of the Array, not the keys from your dictionary

It’s that easy … and yet, because Apple didn’t do it, you have to re-write this class every time you start a new project. Sigh.

There’s one issue with this – for the allKeys method, I did a “copy”, which I probably shouldn’t have (I wasn’t really thinking carefully about i), which means you’ll have to remember to release the reference you receive – or else change that to:

return underlyingArray;

or:

return [[underlyingArray copy] autorelease];

or, perhaps best of all:

return [NSArray arrayWithContentsOfArray: underlyingArray]; // returns an Immutable result; nice and safe!

Here’s my quick hack code, in its entirety:


#import "SortedDictionary.h"

@implementation SortedDictionary
@synthesize underlyingArray, underlyingDictionary;

-(id) init
{
if( self = [super init] )
{
underlyingArray = [[NSMutableArray alloc] init];
underlyingDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}

-(id)objectForKey:(id)aKey
{
return [underlyingDictionary objectForKey:aKey];
}

-(id)valueForKey:(NSString *)key
{
return [underlyingDictionary valueForKey:key];
}

-(NSUInteger)count
{
return [underlyingDictionary count];
}

/** Ordered... */
-(NSArray *)allKeys
{
return [underlyingArray copy];
}

-(NSArray *)allValues
{
return [underlyingDictionary allValues];
}

/** Preserves current order, or does ordered ADD if the key isn't already present */
-(void)setObject:(id)anObject forKey:(id)aKey
{
if( [underlyingDictionary objectForKey:aKey] == nil )
{
[self addObject:anObject forKey:aKey];
}
else
{
[underlyingDictionary setObject:anObject forKey:aKey];
}
}

/** Preserves current order, or does ordered ADD if the key isn't already present */
-(void)setValue:(id)value forKey:(NSString *)key;
{
if( [underlyingDictionary objectForKey:key] == nil )
{
[self addValue:value forKey:key];
}
else
{
[underlyingDictionary setValue:value forKey:key];
}
}

-(void)removeObjectForKey:(id)aKey
{
[underlyingArray removeObject:aKey];
[underlyingDictionary removeObjectForKey:aKey];
}

-(void)removeAllObjects
{
[underlyingArray removeAllObjects];
[underlyingDictionary removeAllObjects];
}

-(void)addObject:(id)anObject forKey:(id)aKey
{
if( [underlyingDictionary objectForKey:aKey] == nil )
{
[underlyingArray addObject:aKey];
[underlyingDictionary setObject:anObject forKey:aKey];
}
else
{
NSLog( @"[%@] ERROR: attempted to addObject a key that already exists in this dictionary: %@", [self class], aKey );
}
}

-(void)addValue:(id)value forKey:(NSString *)key
{
if( [underlyingDictionary objectForKey:key] == nil )
{
[underlyingArray addObject:key];
[underlyingDictionary setValue:value forKey:key];
}
else
{
NSLog( @"[%@] ERROR: attempted to addValue a key that already exists in this dictionary: %@", [self class], key );
}
}

@end

7 Replies to “How to sort an NSDictionary on iPhone”

  1. “It’s that easy … and yet, because Apple didn’t do it, you have to re-write this class every time you start a new project. Sigh.”

    I’m sorry, I’m probably reacting to what’s intended as a dramatic statement, but what part of XCode development makes the reuse of a developer’s personal code library difficult?

    It seems to be a bit like complaining that Extensions are useless because the added methods aren’t automatically visible to all applications.

  2. Nothing stops you from copy/pasting this class from project to project. Just … you end up with hundreds of duplicate copies.

    OR … you have to write (and maintain) an entire library just to contain this, so that you only have one copy of hte source.

    But … now what happens when you update the version? Xcode-for-iPhone is extremely weak at managing library versioning (it works OK on OS X, but a lot of the versioning stuff seems to be disabled for iPhone). You have to regression test against every project you ever made.

    And what about working on different projects for different companies? Are you going to write a one-page license and give it to each client you worked for? (obviously, you don’t *have* to – but not doing so tends to revert to the “many copies lying around all running different versions”).

    Basically: for any fundamental classes, if they’re not inside a standard library, it is a massive pain to deal with them in the long term. This is one of the massive wins that Java brought, and has since been adopted by most new languages. It doesn’t seem that big a deal at first, but once you’re maintaining apps on a regular basis, suddenly it IS a big deal

    (compare this to e.g. PHP, which is stuck with non-standard standard libraries – a horrific mess – where most people end up using the 3rd party libs anyway. Development is slower and more error-prone whenever you do anything in PHP that works with a library, because it’s so easy to not be aware that App X is using a version of Lib Y that you’ve never even seen before).

  3. Thank you for the warning that XCode’s library versioning support is problematic.

    As far as whether a person should carry around a one page license for their personal libraries, especially if they work for more than one company or do consulting? The answer is: YES. Yes, they should, because that’s the sensible way of avoiding having to write MySortedDictionary or MyBufferClassWhichActuallyWorks every single time that the problem comes up again.

    I’ll just be the old, grumpy programmer complaining about kids these days who get so spoiled by having standard libraries that do everything for them that the forget what it was like to do real programming, and go back to keeping the neighbor kids off of my lawn.

  4. @Leon

    Something like this:

    #import

    @interface SortedDictionary : NSObject
    {
    NSMutableArray *underlyingArray;
    NSMutableDictionary *underlyingDictionary;
    }
    @property(nonatomic, retain) NSMutableArray *underlyingArray;
    @property(nonatomic, retain) NSMutableDictionary *underlyingDictionary;

    #pragma mark NSDictionary methods
    -(id)objectForKey:(id)aKey;
    -(id)valueForKey:(NSString *)key;
    -(NSUInteger)count;
    -(NSArray *)allKeys;
    -(NSArray *)allValues;

    #pragma mark NSMutableDictionary methods
    -(void)setObject:(id)anObject forKey:(id)aKey;
    -(void)setValue:(id)value forKey:(NSString *)key;
    -(void)removeObjectForKey:(id)aKey;
    -(void)removeAllObjects;

    #pragma mark Customized NSArray methods
    -(NSUInteger)indexOfKey:(id)anObject;
    -(id)keyAtIndex:(NSUInteger)index;

    #pragma mark custom methods
    -(void)addObject:(id)anObject forKey:(id)aKey;
    -(void)addValue:(id)value forKey:(NSString *)key;

    @end

Comments are closed.