TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | Apache 2 |
ReleasedLast Release | Mar 2016 |
Maintained by Blake Watters, Samuel Giddins.
A simple, powerful Objective-C value transformation API extracted from RestKit
RKValueTransformers is a standalone library that provides a simple value transformation API in Objective-C. Value transformation is the process of converting a value between representations and is a core part of any system that requires that data be transmitted and received in a serialization format distinct from the local data model.
In the general context of a RESTful API this means the transformation between values encoded in an XML or JSON document and local attributes of your data model. The most familiar and obvious example is the transformation of date and time data encoded as a string in a JSON document and represented locally as an NSDate
attribute of an NSObject
or NSManagedObject
derived class. RKValueTransformers provides a simple, well-designed API for generalizing and simplifying the task of handling an arbitrarily complex set of value transformation requirements for your iOS or Mac OS X application.
Value transformation is a core feature of RestKit and RKValueTransformers was extracted from the parent project to benefit the larger Cocoa development community. If you are looking for a comprehensive solution for your RESTful API needs then be sure to give RestKit a closer look.
RKValueTransformers is a "batteries included" library that ships with value transformers handling the most common transformations. The core set of transformers can be customized and new transformers are easily implemented to meet the needs of any application.
NSString
<-> NSURL
NSNumber
<-> NSString
NSArray
<-> NSOrderedSet
NSArray
<-> NSSet
NSDecimalNumber
<-> NSNumber
NSDecimalNumber
<-> NSString
NSNull
<-> nil
NSCoding
<-> NSData
NSNumber
or NSString
<-> NSDate
NSDate
(Only supports complete timestamp strings. On 32 bit systems such as iOS devices pre-iPhone 5s only years < 2038 are supported)stringValue
-> NSString
NSArray
, NSSet
, NSOrderedSet
and their mutable counterparts)NSDictionary
(object becomes a key for empty nested dictionary)NSMutableCoding
-> mutable representation of itselfNSString
<-> NSDate
via NSDateFormatter
. Default formats include:
NSString
<-> NSNumber
via NSNumberFormatter
RKValueTransforming
protocol, subclassing RKValueTransformer
or with blocks via RKBlockValueTransformer
.RKCompoundValueTransformer
class.All value transformation is performed via an abstract common interface defined by the RKValueTransforming
protocol:
NSString *stringContainingDecimalNumber = @"3.4593895835";
NSError *error = nil;
NSDecimalNumber *decimalNumber = nil;
BOOL success = [[RKValueTransformers decimalNumberToStringValueTransformer] transformValue:stringContainingDecimalNumber toValue:&decimalNumber ofClass:[NSDecimalNumber class] error:&error];
The transformValue:toValue:ofClass:error:
method is always the same regardless of the implementation details of the underlying transformation. It is guaranteed to always return a Boolean value indicating if the transformation was successful and value transformers must return an NSError
in the event the transformation could not be performed.
In many cases, whether or not a given transformation can be performed can be determined entirely by the types involved in the transformation. In these cases, a value transformer may implement the optional RKValueTransforming
method validateTransformationFromClass:(Class)inputValueClass toClass:(Class)outputValueClass
:
BOOL isTransformationPossible = [[RKValueTransformers arrayToSetValueTransformer] validateTransformationFromClass:[NSSet class] toClass:[NSArray class]];
NSAssert(isTransformationPossible == YES, @"Should be `YES`");
isTransformationPossible = [[RKValueTransformers arrayToSetValueTransformer] validateTransformationFromClass:[NSSet class] toClass:[NSData class]];
NSAssert(isTransformationPossible == NO, @"Should be `NO`");
Note that as this is an optional method you must check that a given instance responds to the validation selector. If it does not then the transformation cannot be validated and a transformation must be attempted to determine success or failure.
Individual transformers are very convenient -- they abstract away the need to remember how to implement a given transformation and present a simple interface for transformations. But the real power of RKValueTransformers emerges when you assemble a collection of value transformers into a compound transformer via the RKCompoundValueTransformer
class. Compound value transformers also implement the RKValueTransforming
protocol -- but instead of providing any value transformation and validation themselves they proxy the calls to a collection of underlying value transformers in programmer defined order. This allows you to configure a set of transformers in a specific order such that the first transformer that is capable of performing a given transformation will handle it.
Consider for example that a given application may interact with several API's that return dates as strings in several different formats. We wish to be able to transform any given string value into an NSDate
without worrying about the details. We could configure a compound transformer to handle this task like so:
NSArray *dateFormats = @[ @"MM/dd/yyyy", @"yyyy-MM-dd'T'HH:mm:ss'Z'", @"yyyy-MM-dd" ];
RKCompoundValueTransformer *compoundValueTransformer = [RKCompoundValueTransformer new];
for (NSString *dateFormat in dateFormats) {
NSDateFormatter *dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = dateFormat;
[compoundValueTransformer addValueTransformer:dateFormatter];
}
[compoundValueTransformer addValueTransformer:[RKValueTransformer timeIntervalSince1970ToDateValueTransformer]];
NSArray *dateStrings = @[ @"11/27/1982", @"1378767519.18176508", @"2013-11-27", @"2013-04-23T16:29:05Z" ];
NSError *error = nil;
for (NSString *dateString in dateStrings) {
NSDate *date = nil;
BOOL success = [compoundValueTransformer transformValue:dateString toValue:&date ofClass:[NSDate class]];
NSLog(@"Transformed value '%@' to value '%@' successfully=%@, error=%@", dateString, date, success ? @"YES" : @"NO", error);
}
RKValueTransformers supports the creation of ad-hoc value transformer instances implemented via blocks. For example, one could implement a value transformer that turns all NSString
instances into uppercase strings like so:
RKValueTransformer *uppercaseStringTransformer = [RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class sourceClass, __unsafe_unretained Class destinationClass) {
// We transform a `NSString` into another `NSString`
return ([sourceClass isSubclassOfClass:[NSString class]] && [destinationClass isSubclassOfClass:[NSString 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, [NSString class], error);
// Perform the transformation
*outputValue = [(NSString *)inputValue uppercaseString];
return YES;
}];
RKValueTransformers is extremely lightweight and has no direct dependencies outside of the Cocoa Foundation framework. As such, the library can be trivially be installed into any Cocoa project by directly adding the source code. Despite this fact, we recommend installing via CocoaPods as it provides modularity and enables the easy installation of new value transformers that are dependent on RKValueTransformers itself.
Simply add RKValueTransformers.h
and RKValueTransformers.m
to your project and #import "RKValueTransformers.h"
.
RKValueTransformers is designed to be simple to integrate and use. The entire library consists of a single protocol, three classes, and a handful of category implementations:
RKValueTransforming
- Defines the value transformation API. Adopted by any class that wishes to act as a value transformer.RKValueTransformer
- An abstract base class that implements RKValueTransforming
. The base class includes static accessors for retrieving singleton instances of the bundled value transformers. Extension libraries can subclass RKValueTransformer
to provide new transformers.RKBlockValueTransformer
- A concrete subclass of RKValueTransformer
that enables the creation of ad-hoc value transformers defined via blocks.RKCompoundValueTransformer
- A concrete implementation of RKValueTransforming
that proxies calls to an underlying collection of value transformers and provides support for composing value transformers.For those implementing value transformers, a few macros are included to simplify the implementation of validation and transformation methods:
RKValueTransformerTestInputValueIsKindOfClass
- Tests that a given input value is an instance of a given class or one of its subclasses. If the test evaluates negatively, then NO
is returned and an appropriate NSError
is emitted.RKValueTransformerTestOutputValueClassIsSubclassOfClass
- Tests that a given output value class is equal to a given class or is a subclass there of. If the test evaluates negatively, then NO
is returned an appropriate NSError
is emitted.RKValueTransformerTestTransformation
- Tests that a given transformation was successful. If the test evaluates negatively, then NO
is returned an appropriate NSError
is emitted.In developing RKValueTransformers we looked closely at NSValueTransformer
and ultimately determined that it was not a great fit for our needs. Specifically we found the following issues:
NSValueTransformer
defines a notion of 'forward' and 'reverse' transformation that doesn't map cleanly in a system primarilly concerned with type transformations. Which side do you consider forward? This gets worse when you consider transformations that can occur between more than just two types.NSValueTransformer
exposes the class of the "output" object via the class method transformedValueClass
. This becomes annoying as you are forced to use inheritance to express type knowledge. This necessitates directly inheriting from NSValueTransformer
or using fancy run-time hackery such as that utilized by TransformerKit.NSValueTransformer
exposes a single global name based registry for value transformers via the setValueTransformer:forName:
and valueTransformerForName:
methods. Ultimately this is not granular enough to provide necessary flexibility and requires the use of names (as opposed to type information) to look up transformers.Given all of the above it just made sense to go back to a clean slate and design a solution to the value transformation problem from scratch.
RKValueTransformers is tested using the Expecta library of unit testing matchers. In order to run the tests, you must do the following:
pod install
open RKValueTransformers.xcworkspace
Blake Watters
Samuel E. Giddins
RKValueTransformers is available under the Apache 2 License. See the LICENSE file for more info.