NestedObjectSetters 1.1

NestedObjectSetters 1.1

TestsTested
LangLanguage Obj-CObjective C
License MIT
ReleasedLast Release Dec 2014

Maintained by Unclaimed.



  • By
  • Ryan Maxwell

Categories on NSMutableDictionary and NSUserDefaults that enable setting nested objects via key paths.

Purpose

I often want to set a value multiple levels deep in a mutable dictionary. Unfortunately this will fail if the nested mutable dictionary does not exist. The following statement outputs null:

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"a"][@"b"] = @"foo";

NSLog(@"%@", dict[@"a"][@"b"]);

Slightly rewriting the statement makes it more obvious:

[[dict objectForKey:@"a"] setObject:@"foo" forKey:@"b"];

The first objectForKey: method returns nil, and the subsequent setObject:ForKey: fails silently.

Another common case is when I make a mutable copy of a dictionary - any dictionaries contained within the dictionary still remain immutable, which leads to annoying, error-prone code like this to simply set a new value at anything other than the root level:

NSDictionary *colorSets = @{
    @"ColorsOfTheRainbow": @{
        @"Red": @"#F00",
        @"Yellow": @"#FF0",
        @"Pink": @"#F0F",
        @"Green": @"#0F0",
        @"Purple": @"#8000FF",
        @"Orange": @"#F80"
    }
};

/* Create mutable copy of dictionary */
NSMutableDictionary *newColorSets = [colorSets mutableCopy];

/* Get second-level dictionary */
NSDictionary *currentColorsOfTheRainbow = [colorSets objectForKey:@"ColorsOfTheRainbow"];

/* Check that the second level dictionary existed. If so - create a mutable copy; if not - create a new mutable dictionary to use */
NSMutableDictionary *newColorsOfTheRainbow = (currentColorsOfTheRainbow) ? [currentColorsOfTheRainbow mutableCopy] : [NSMutableDictionary dictionary];

/* Set the value in the new dictionary */
[newColorsOfTheRainbow setObject:@"#00F" forKey:@"Blue"];

/* Set the second-level dictionary back into the mutable dictionary */
[newColorSets setObject:newColorsOfTheRainbow forKey:@"ColorsOfTheRainbow"];

Category Interface

The following two instance methods are added to NSMutableDictionary and NSUserDefaults

- (void)setObject:(id)object forKeyPath:(NSString *)keyPath;

- (void)setObject:(id)object
       forKeyPath:(NSString *)keyPath
createIntermediateDictionaries:(BOOL)createIntermediates
    replaceIntermediateObjects:(BOOL)replaceIntermediates;

The first method is a convenience method for the second – passing YES as both parameters – as that is likely the most wanted behavior.

  • createIntermediateDictionaries creates any necessary dictionaries that do not exist while traversing the key path.
  • replaceIntermediateObjects replaces any returned objects in the key path that are not a subclass of an NSDictionary.

The above examples could simply be achieved with:

[dict setObject:@"foo" forKeyPath:@"a.b"];

and

[newColorSets setObject:@"#00F" forKeyPath:@"ColorsOfTheRainbow.Blue"];

Category Method Prefix

The methods are prefixed with nos_ by default, to avoid any namespace collisions.

To use the methods without the prefix, add #define NESTEDOBJECTSETTERS_NO_PREFIX 1 to your project’s Prefix file, or NESTEDOBJECTSETTERS_NO_PREFIX=1 to your Target’s Build Settings in the Preprocessor Macros section.

Category Requirements

The categories are compatible with both ARC and traditional retain/release code, and can be used on all versions of iOS and OS X.

Project Requirements

  • This Xcode project uses the XCTest framework and so requires >= Xcode 5

Usage

  • Manual Install: Add NestedObjectSetters.h/m to your project.
  • Cocoapods: pod 'NestedObjectSetters'