Earlier this week GitHub announced Mantle, a model framework for Objective-C that is lighter weight than Core Data and well suited to implementing a model layer for an app that interacts with web services. The project is available on GitHub and also as a Cocoapods pod and works with iOS and Mac OS X apps.
I’ve been working on an Objective-C implementation of the Cosm API using the excellent AFNetworking framework with model objects as simple NSObject subclasses. After reading about Mantle I wanted to take a look at how much work I could save myself so updated one of my model objects to be an MTLModel subclass instead.
Before…
@implementation CBCosmDatastream
- (id)initWithAttributes:(NSDictionary*)attributes
{
self = [super init];
if (self)
{
if (!attributes)
{
return nil;
}
for (NSString* key in attributes)
{
[self setValue:[attributes valueForKey:key]
forKey:key];
}
}
return self;
}
#pragma mark - Equality
- (NSUInteger)hash
{
return [self.datastreamId hash];
}
- (BOOL)isEqual:(id)object
{
if ([object isKindOfClass:[CBCosmDatastream class]])
{
CBCosmDatastream* otherDatastream = (CBCosmDatastream*)object;
return [self.datastreamId isEqualToNumber:otherDatastream.datastreamId];
}
else
{
return NO;
}
}
#pragma mark - KVC
- (void)setValue:(id)value forKey:(NSString*)key
{
if ([key isEqualToString:@"id"])
{
[super setValue:value forKey:@"datastreamId"];
}
else if ([key isEqualToString:@"at"])
{
[super setValue:value forKey:@"updated"];
}
else if ([key isEqualToString:@"current_value"])
{
[super setValue:value forKey:@"value"];
}
else if ([key isEqualToString:@"min_value"])
{
[super setValue:value forKey:@"minValue"];
}
else if ([key isEqualToString:@"max_value"])
{
[super setValue:value forKey:@"maxValue"];
}
else if ([key isEqualToString:@"datapoints"])
{
if ([value isKindOfClass:[NSArray class]])
{
NSArray* values = (NSArray*)value;
NSMutableArray* datapoints = [NSMutableArray arrayWithCapacity:[values count]];
for (NSDictionary* attributes in values)
{
CBCosmDatapoint* datapoint = [[CBCosmDatapoint alloc] initWithAttributes:attributes];
[datapoints addObject:datapoint];
}
_datapoints = datapoints;
}
}
}
@end
and after…
@implementation CBCosmDatastream
#pragma mark - Mantle
+ (NSDictionary *)externalRepresentationKeyPathsByPropertyKey
{
return [super.externalRepresentationKeyPathsByPropertyKey mtl_dictionaryByAddingEntriesFromDictionary:@{
@"datastreamId": @"id",
@"updated": @"at",
@"value": @"current_value",
@"minValue": @"min_value",
@"maxValue": @"max_value"
}];
}
#pragma mark - KVC
- (void)setValue:(id)value forKey:(NSString*)key
{
if ([key isEqualToString:@"datapoints"])
{
if ([value isKindOfClass:[NSArray class]])
{
NSArray* values = (NSArray*)value;
NSMutableArray* datapoints = [NSMutableArray arrayWithCapacity:[values count]];
for (NSDictionary* attributes in values)
{
CBCosmDatapoint* datapoint = [[CBCosmDatapoint alloc] initWithAttributes:attributes];
[datapoints addObject:datapoint];
}
_datapoints = datapoints;
}
}
}
@end
I was able to remove most of my boilerplate code and will be able to replace the setValue:forKey: method with a NSValueTransformer after updating the CBCosmDatapoint class to be an MTLModel subclass. I replaced the initWithAttributes: method calls in my unit tests with ones to initWithDictionary: and passed all my tests without any other changes with just a few minutes of work. I like having less code to maintain and will definitely be updating the rest of my model objects.
Mantle is early days at only version 0.2 but a project definitely to keep an eye on for your iOS and OS X apps.