ValueTransformer
Of all the Foundation classes,
Value
is perhaps the one that fared the worst
in the shift from macOS to iOS.
Why? Here are two reasons:
First,
Value
was used primarily in AppKit with Cocoa bindings.
There, they could automatically transform values from one property to another,
like for negating a boolean or checking whether a value was nil
,
without the need of intermediary glue code.
iOS doesn’t have bindings.
The second reason has less to do with iOS than the Objective-C runtime itself.
With the introduction of blocks,
it got a whole lot easier to pass behavior between objects —
significantly easier than, say Value
or NSInvocation
.
So even if iOS were to get bindings tomorrow,
it’s unclear whether Value
would play a significant role this time around.
But you know what?
Value
might just be ripe for a comeback.
With a little bit of re-tooling and some recontextualization,
this blast from the past could be the next big thing in your application.
Value
is an abstract class that transforms one value into another.
A transformation specifies what kinds of input values can be handled
and whether it supports reversible transformations.
A typical implementation looks something like this:
class Class Name Transformer: Value Transformer {
override class func transformed Value Class() -> Any Class {
return NSString.self
}
override class func allows Reverse Transformation() -> Bool {
return false
}
override func transformed Value(_ value: Any?) -> Any? {
guard let type = value as? Any Class else { return nil }
return NSString From Class(type)
}
}
@interface Class Name Transformer: NSValue Transformer {}
@end
#pragma mark -
@implementation Class Name Transformer
+ (Class)transformed Value Class {
return [NSString class];
}
+ (BOOL)allows Reverse Transformation {
return NO;
}
- (id)transformed Value:(id)value {
return (value == nil) ? nil : NSString From Class([value class]);
}
@end
Value
is rarely initialized directly.
Instead, it follows a pattern familiar to fans of
NSPersistent
or NSURLProtocol
,
where a class is registered and instances are created from a manager —
except in this case, you register a named instance to act as a singleton:
extension Class Name Transformer {
static let name = NSValue Transformer Name(raw Value: "Class Name Transformer")
}
// Set the value transformer
Value Transformer.set Value Transformer(Class Name Transformer(),
for Name: Class Name Transformer.name)
// Get the value transformer
let value Transformer = Value Transformer(for Name: Class Name Transformer.name)
NSValue Transformer Name const Class Name Transformer Name = @"Class Name Transformer";
// Set the value transformer
[NSValue Transformer set Value Transformer:[[Class Name Transformer alloc] init] for Name:Class Name Transformer Name];
// Get the value transformer
NSValue Transformer *value Transformer = [NSValue Transformer value Transformer For Name:Class Name Transformer Name];
A common pattern is to register the singleton instance
in the +initialize
method of the value transformer subclass
so it can be used without additional setup.
Now at this point you probably realize Value
’s fatal flaw:
it’s super annoying to set up!
Create a class,
implement a handful of simple methods,
define a constant,
and register it in an +initialize
method? No thanks.
In this age of blocks, we want — nay, demand — a way to declare functionality in one (albeit gigantic) line of code.
Nothing a little metaprogramming can’t fix. Behold:
let TKCapitalized String Transformer Name =
NSValue Transformer Name(raw Value: "TKCapitalized String Transformer Name")
Value Transformer.register Value Transformer With Name(TKCapitalized String Transformer Name,
transformed Value Class:NSString.self) { object in
guard let string = object as? String else { return nil }
return string.capitalized
}
NSValue Transformer Name const TKCapitalized String Transformer Name = @"TKCapitalized String Transformer Name";
[NSValue Transformer register Value Transformer With Name:TKCapitalized String Transformer Name
transformed Value Class:[NSString class]
returning Transformed Value With Block:^id(id value) {
return [value capitalized String];
}];
Now with a fresh new look,
we can start to get a better understanding of
how we might take advantage of Value
:
Making Business Logic More Functional
Value
objects are a great way to represent
an ordered chain of fixed transformations.
For instance, an app interfacing with a legacy system
might transform user input through a succession of string transformations
(trim whitespace, remove diacritics, and then capitalize letters)
before sending it off to the mainframe.
Thinking Forwards and Backwards
Unlike blocks, value transformers have the concept of reversibility, which enables some interesting use cases.
Say you were wanted to map keys from a REST API representation into a model.
You could create a reversible transformation that converted snake_case
to llama
when initializing,
and llama
to snake_case
when serializing back to the server.
Configuring Functionality
Another advantage over blocks is that
Value
subclasses can expose new properties
that can be used to configure behavior in a particular way.
Access to properties also provides a clean way to cache or memoize results
and do any necessary book-keeping along the way.
Transforming Your Core Data Stack
Lest we forget,
Value
can be used alongside Core Data
to encode and decode compound data types from blob fields.
It seems to have fallen out of fashion over the years,
but serializing simple collections in this way
can be a winning strategy for difficult-to-model data.
(Just don’t use this approach to serialize images or other binary data;
use external storage instead)
Value
,
far from a vestige of AppKit,
remains Foundation’s purest connection to functional programming:
input goes in, output comes out.
While it’s true that Objective-C blocks
and all of the advanced language features in Swift
are superior examples of the functional programming paradigm.
Value
has a special place in Cocoa’s history and Xcode’s tooling.
For that reason, object orientation is transformed
from an embarrassing liability to its greatest asset.
And though it hasn’t aged very well on its own,
a little modernization restores Value
to that highest esteem of NSHipsterdom:
a solution that we didn’t know we needed
but was there all along.