What is NSValueTransformer and when should you use it?

July 21, 2014

NSValueTransformer does exactly what its name suggests, takes a value of any kind and transforms it into another one, think of it as a function encapsulated within an object.

Ok, but how does that help you? One of the most appropriate use case for NSValueTransformer is transforming data from an ill-defined form into a canonical form.

Think of the following scenario. You have to connect to a series of web services which return basically the same information, let’s say weather information, however, in different formats, some are maybe using JSON, others XML. Moreover, even for the ones that return the data in the same format, the structure, keys or values (e.g. temperature could be expressed in either °C or °F) differs.

A good design to solve this problem should, at least, allow you to add new services easily and not mix your business logic with the service data parsing and extracting.

Here is where NSValueTransformer comes really handy. You could have different NSValueTransformer subclasses for each service, transforming the received http NSData into a canonical NSDictionary or a custom class that could be used throughout your app. This gives you the possibility to add new services just by creating a new NSValueTransformer subclass that knows how to make this conversion happen. Below there’s an example on how one of this NSValueTransformer subclass implementation might look like.

+ (Class)transformedValueClass {
    return NSDictionary.class;
}

+ (BOOL)allowsReverseTransformation {
    return NO;
}

- (id)transformedValue:(NSData*)data {

    //the respones might not be what we''re expecting,
    //so we''re interested in the error, if any
    NSError* error;

    //we assume this service response is JSON
    NSMutableDictionary* response =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];

    //we let others know if the transform ended with an error
    //and return right away if it did
    self.error = error;
    if (self.error) return nil;


    //our canonical response
    NSMutableDictionary* canonicalResponse = [NSMutableDictionary dictionary];

    //we copy the values we''re interested in into the canonical dictionary
    //using the appropiate keys and transforming values where needed
    canonicalResponse[@"temperature"] = [self fahrenheitToCelsius:response[@"temp"]];
    canonicalResponse[@"location"] = [response[@"area"] capitalizedString];

    //and so on
    //...


    //the returned dictionary has a consistent
    //structure for each and every service now
    return canonicalResponse;
}

There you go, this little gem just saved you from a lot of tangled ifs and cases.

Comments

comments powered by Disqus