Skip to content

RKValueTransformer Example: Mapping NSString to NS_ENUM

mozeryansky edited this page Jun 10, 2015 · 4 revisions

Here is an example on how to transform NSString values into a corresponding NS_ENUM.

We will create a RKValueTransformer and attach it to a specific attribute mapping, which we will then add the attribute mapping as a property mapping. In this example we will use a RKBlockValueTransformer, but you can use anything that adopts the protocol RKValueTransforming.

NS_ENUM will be the type you defined (i.e. NSInteger), so you will need compare the string value and return a NSNumber set to the corresponding NSInteger value for the enumerated type. The below example uses a dictionary to map NSStrings to NSNumbers. As long as you return a NSNumber this will work.

typedef NS_ENUM(NSInteger, BarnAnimal){
        BarnAnimalUnknown, // I use a unknown type for invalid returned values
        BarnAnimalPig,
        BarnAnimalCow,
        BarnAnimalGoat
}

RKValueTransformer *enumTransformer = [RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class sourceClass, __unsafe_unretained Class destinationClass) {
        // We transform a NSString into a NSNumber
        return ([sourceClass isSubclassOfClass:[NSString class]] && [destinationClass isSubclassOfClass:[NSNumber class]]);

} transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, Class outputValueClass, NSError *__autoreleasing *error) {
        // Validate the input and output
        RKValueTransformerTestInputValueIsKindOfClass(inputValue, [NSString class], error);
        RKValueTransformerTestOutputValueClassIsSubclassOfClass(outputValueClass, [NSNumber class], error);

        // Perform the transformation
        NSDictionary *enumMap = @{
            @"Pig" : @(BarnAnimalPig),
            @"Cow" : @(BarnAnimalCow),
            @"Goat" : @(BarnAnimalGoat)
        };

        NSNumber *enumNum = enumMap[inputValue];
        if (!enumNum) {
            enumNum = @(BarnAnimalUnknown);
        }

        *outputValue = enumNum;
        return YES;
    }];

    RKAttributeMapping *enumMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"farmAnimal" toKeyPath:@"farmAnimal"];
    enumMapping.valueTransformer = enumTransformer;


    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[self class]];
    [mapping addPropertyMapping:enumMapping];

Why does this work? You would be correct to ask this question, a NSNumber is not a NSInteger. However, RestKit uses KVC to perform the setting of the variables, specifically setValue:forKeyPath. KVC has handling for scalar types by performing wrapping and unwrapping: Scalar and Structure Support. In short, KVC will tell RestKit your NSInteger is an NSNumber, and when RestKit sets the NSNumber, KVC will convert it to an NSInteger.