Introduction
NSDictionary in Objective-C (and Dictionary in Swift) does not guarantee key order. If you need ordered keys, use a separate NSArray to track key order alongside the dictionary, use NSOrderedSet for unique ordered keys, or create a custom ordered dictionary wrapper. In Swift, third-party libraries like OrderedDictionary from the Swift Collections package provide this functionality directly.
NSDictionary Is Unordered
1NSDictionary *dict = @{
2 @"banana": @2,
3 @"apple": @1,
4 @"cherry": @3
5};
6
7// Key order is NOT guaranteed
8for (NSString *key in dict) {
9 NSLog(@"%@: %@", key, dict[key]);
10}
11// May print in any order — not necessarily insertion order
Solution 1: Separate Key Array
The simplest approach — maintain an array of keys in the desired order:
1NSArray *orderedKeys = @[@"apple", @"banana", @"cherry"];
2NSDictionary *dict = @{
3 @"banana": @2,
4 @"apple": @1,
5 @"cherry": @3
6};
7
8// Iterate in order
9for (NSString *key in orderedKeys) {
10 NSLog(@"%@: %@", key, dict[key]);
11}
12// apple: 1
13// banana: 2
14// cherry: 3
For mutable dictionaries:
1NSMutableArray *orderedKeys = [NSMutableArray array];
2NSMutableDictionary *dict = [NSMutableDictionary dictionary];
3
4// Add in order
5[orderedKeys addObject:@"first"];
6dict[@"first"] = @1;
7
8[orderedKeys addObject:@"second"];
9dict[@"second"] = @2;
10
11// Remove
12[orderedKeys removeObject:@"first"];
13[dict removeObjectForKey:@"first"];
Solution 2: Custom Ordered Dictionary Class
1@interface OrderedDictionary<KeyType, ObjectType> : NSObject
2
3- (void)setObject:(ObjectType)object forKey:(KeyType<NSCopying>)key;
4- (ObjectType)objectForKey:(KeyType)key;
5- (void)removeObjectForKey:(KeyType)key;
6- (NSArray<KeyType> *)allKeys;
7- (NSUInteger)count;
8- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(KeyType key, ObjectType obj, BOOL *stop))block;
9
10@end
11
12@implementation OrderedDictionary {
13 NSMutableArray *_keys;
14 NSMutableDictionary *_dict;
15}
16
17- (instancetype)init {
18 self = [super init];
19 if (self) {
20 _keys = [NSMutableArray array];
21 _dict = [NSMutableDictionary dictionary];
22 }
23 return self;
24}
25
26- (void)setObject:(id)object forKey:(id<NSCopying>)key {
27 if (![_dict objectForKey:key]) {
28 [_keys addObject:key];
29 }
30 [_dict setObject:object forKey:key];
31}
32
33- (id)objectForKey:(id)key {
34 return [_dict objectForKey:key];
35}
36
37- (void)removeObjectForKey:(id)key {
38 [_dict removeObjectForKey:key];
39 [_keys removeObject:key];
40}
41
42- (NSArray *)allKeys {
43 return [_keys copy];
44}
45
46- (NSUInteger)count {
47 return [_keys count];
48}
49
50- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block {
51 [_keys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) {
52 block(key, self->_dict[key], stop);
53 }];
54}
55
56@end
Usage:
1OrderedDictionary *od = [[OrderedDictionary alloc] init];
2[od setObject:@1 forKey:@"apple"];
3[od setObject:@2 forKey:@"banana"];
4[od setObject:@3 forKey:@"cherry"];
5
6[od enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *value, BOOL *stop) {
7 NSLog(@"%@: %@", key, value);
8}];
9// apple: 1, banana: 2, cherry: 3 (insertion order guaranteed)
Solution 3: Sorted Keys
If you want alphabetical order (not insertion order):
1NSDictionary *dict = @{@"banana": @2, @"apple": @1, @"cherry": @3};
2
3NSArray *sortedKeys = [[dict allKeys] sortedArrayUsingSelector:@selector(compare:)];
4for (NSString *key in sortedKeys) {
5 NSLog(@"%@: %@", key, dict[key]);
6}
7// apple: 1, banana: 2, cherry: 3
8
9// Custom sort
10NSArray *customSorted = [[dict allKeys] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
11 return [dict[a] compare:dict[b]]; // Sort by value
12}];
Swift: OrderedDictionary (Swift Collections)
1// Add to Package.swift:
2// .package(url: "https://github.com/apple/swift-collections", from: "1.0.0")
3
4import OrderedCollections
5
6var ordered = OrderedDictionary<String, Int>()
7ordered["apple"] = 1
8ordered["banana"] = 2
9ordered["cherry"] = 3
10
11// Iteration preserves insertion order
12for (key, value) in ordered {
13 print("\(key): \(value)")
14}
15// apple: 1, banana: 2, cherry: 3
16
17// Access by index
18print(ordered.elements[0]) // (key: "apple", value: 1)
Swift: Manual Approach
1struct OrderedDict<Key: Hashable, Value> {
2 private var keys: [Key] = []
3 private var dict: [Key: Value] = [:]
4
5 mutating func set(_ value: Value, forKey key: Key) {
6 if dict[key] == nil {
7 keys.append(key)
8 }
9 dict[key] = value
10 }
11
12 func get(_ key: Key) -> Value? {
13 return dict[key]
14 }
15
16 var orderedKeys: [Key] { keys }
17 var orderedValues: [Value] { keys.compactMap { dict[$0] } }
18}
Common Pitfalls
Assuming NSDictionary preserves insertion order: NSDictionary and Swift Dictionary do not guarantee any key ordering. Even if keys appear ordered in small tests, the order can change with different data sizes or runtime conditions.
Using allKeys and expecting consistent order: [dict allKeys] returns keys in an arbitrary order that may differ between calls (though it is typically stable within a single run). Never rely on it for ordered iteration.
Not keeping the key array in sync: When using a separate NSMutableArray for ordered keys, forgetting to add/remove keys when modifying the dictionary causes inconsistencies. Always wrap both operations in a helper method.
Performance of linear search in key array: [NSMutableArray removeObject:] and containsObject: are O(n). For large dictionaries (1000+ keys), this becomes slow. Use NSMutableOrderedSet instead of NSMutableArray for O(1) lookups.
JSON serialization losing order: NSDictionary serialized to JSON via NSJSONSerialization does not preserve key order. If key order matters for the JSON output, serialize manually or use an ordered JSON library.
Summary
NSDictionary and Swift Dictionary are unordered — key iteration order is not guaranteed
Maintain a separate NSArray/Array of keys alongside the dictionary for insertion order
Use the Swift Collections package (OrderedDictionary) for a production-ready ordered dictionary
Sort keys with sortedArrayUsingSelector: or sorted() for alphabetical order
Wrap key array + dictionary operations in a custom class to keep them synchronized
For JSON output with ordered keys, use a specialized serialization approach