Updated some info about Mantle

I haven’t been blogging too much recently. My bad. In the meantime I’ve been working on my own App, a Songbook App that allows MIDI control so that if you want to change pages and your hands are busy playing instruments, you can use something like a footswitch controller to change the page.

Check out my Portfolio page for that.

If you’re not a music person, here’s a quick post to tell you that I updated my post about the Mantle Framework. It discusses some findings about working with primitives in your data models, something that wasn’t quite clear to me. In short, Mantle is awesome and does that all for you.

Indie Game Development and BocceBeers v2.0

Now that I’ve got a bit of down time and am on holiday, I thought I’d dust off the code of my very first software project in my career BocceBeers, and see if I can give it some of the upgrades it’s been screaming for since iOS 3.0.

Refactoring old code is a funny endeavour!  Seeing as BocceBeers was the first thing I ever wrote, completely on my own, not much help other than forums, I’m amazed at 2 things:  That they game runs as well as it does, and how messy the code was!

Basically, I’ve had to majorly rewrite, rename, convert to ARC, ensure better decoupling and encapsulation just to get the game more or less running again.  That was the boring part.  But then it moved on to more interesting things…

My goals for this upgrade are to make it universal and playable on the iPad, adding retina support, and make it possible to have turn-based play over the internet.  If I find time, I will also look into incorporating an instant replay system I had been working on.

(On a cheesy note, I should add that the inspiration to do this now as that I gave my parents an iPad mini recently and back in 2010 when I wrote the original BocceBeers while home at my parents’ on holiday, my mom was my guinea pig game tester.  So now that she has an iPad, the proud mom that she is, asked me: “Can I play your game on this?”  So I thought, I’m gonna improve this game!)

As such, having become more of a UIKit expert than Cocos2D (on which BocceBeers 1.0 is written), I thought I’d rip out all the front end classes implemented in Cocos2D and use UIKit.  This makes it a lot faster to me to write UI Code, and UIKit is really an amazing framework that we all just accept but probably rarely marvel at the technological genius of Apple Engineers.  Now that I’m 3 years more experienced, I’m finding Cocos2D to be a great framework, but there are some oddities about it, just sometimes even down to strange naming conventions.

Anyhow, the whole point of this post is to say I’m loving this job, and I think my true calling in life is probably to be an indie games developer because of the total freedom it allows and it’s highly creative and most importantly fun!  That said, any iOS development has been fun.

Hands-on with the Mantle Model Framework

I had initially heard about Mantle on github ages ago when it was really at the beginning of its development and thought it was a great idea since I’ve worked on numerous apps that require some simple persistence, but without a complex object graph and I could never bring myself to justify the behemoth that is CoreData, just to persist a few object types.

So, familiar story, I wrote a lot of boiler plate code for

- (id)initWithDictionary:(NSDictionary*)jsonDict;

and the methods of the NSCoding and NSCopying protocols, as well as the comparison methods. A lot of boring code to write. Seriously boring code.

Now that I have a bit of time off, I thought I’d get my hands dirty with Mantle. It aims to remove a lot of this boiler plate code while being flexible and without being too complicated.

Can this be confirmed to be true? Well… mostly. That’s what this post is for. The documentation was a bit out of date, so I had to get myself oriented with how it actually works vs. how it is sold to the potential adopter (i.e. you).

Misleading

The documentation seems to imply that you basically have to declare properties, then write a

+ (NSValueTransformer*)propertyNameTransformer;

to make it work, then instantiate with

- (id)initWithDictionary:(NSDictionary*)jsonDict;

If this is what you assume is how it works, you may run into problems. I suppose the lesson learned is don’t assume how something works until you really examine the code. I had hoped that this “here! easy to get up and running!” documentation was that, but it wasn’t.

Back on Track

The API has changed somewhat since that documentation was written. Let me just post my Model implementation and you can see for yourself how it works for different types of properties. Still not sure how it deals with primitive types, but there are quite a few features in the library that I still need to learn about. These are the basics that should help you get going faster than I did.

//  HS7UserModel.h
//
//  Created by Stephen O'Connor on 5/26/13.

#import "Mantle.h"

// to map key-value pairs in the incoming JSON to properties on your model, you need to
// support the MTLJSONSerializing protocol then instantiate your objects with the factory method
// [MTLJSONAdapter modelOfClass: fromJSONDictionary: error:]

@interface HS7UserModel : MTLModel

@property (nonatomic, readonly, copy) NSNumber *identifier;  // i.e. userID
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, copy) NSURL *url;  // i.e. get this object

@property (nonatomic, readonly, copy) NSDate *createdAt;
@property (nonatomic, readonly, copy) HS7UserModel *bestFriend;
@property (nonatomic, readonly, copy) NSArray *closestFriends;

@end

And then the .m file.

//  HS7UserModel.m
//
//  Created by Stephen O'Connor on 5/26/13.

#import "HS7UserModel.h"

@implementation HS7UserModel

// keys in the JSON you care about, and which property they map to.  @{ localPropertyName : jsonKey, ... }
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"identifier"  : @"id",
             @"name"        : @"name",
             @"url"         : @"user_url",
             @"createdAt"   : @"created_at",
             @"bestFriend"  : @"best_friend",
             @"closestFriends" : @"friends"
             };
}

// note the pattern JSONTransformer
+ (NSValueTransformer *)urlJSONTransformer
{
    return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

// mapping a nested Model
+ (NSValueTransformer *)bestFriendJSONTransformer {

    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSDictionary *userDict) {
        return [MTLJSONAdapter modelOfClass: HS7UserModel.class
                         fromJSONDictionary: userDict
                                      error: nil];
    } reverseBlock:^(HS7UserModel *user) {
        return [MTLJSONAdapter JSONDictionaryFromModel: user];
    }];
}

+ (NSValueTransformer*)closestFriendsJSONTransformer
{
    return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:[HS7UserModel class]];
}

// mapping a NSDate with formats specific to your application's back end
+ (NSValueTransformer *)completedAtJSONTransformer {

    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
        return [self.dateFormatter dateFromString:str];
    } reverseBlock:^(NSDate *date) {
        return [self.dateFormatter stringFromDate:date];
    }];
}

// by the way, creating NSDateFormatters is expensive.  So we create a static instance...
+ (NSDateFormatter *)dateFormatter {

    static NSDateFormatter *kDateFormatter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        kDateFormatter = [[NSDateFormatter alloc] init];
        kDateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        kDateFormatter.dateFormat = @"yyyy-MM-dd";  // you configure this based on the strings that your webservice uses!!
    });

    return kDateFormatter;
}

@end

Then instantiating objects is as simple as:


NSDictionary *jsonDict = kSomeParsedJSONDictionary;  // ... some user json you already have defined
NSError *error = nil;  // yes, error handling!

HS7UserModel *aUser = [MTLJSONAdapter modelOfClass: HS7UserModel.class fromJSONDictionary: jsonDict error: &error];

if (error){
    // it will tell you what went wrong in the whole conversion process via this error.
}

So, there it is. I hope this should help get you a bit better oriented with the framework. These really are still just the basics, and I’m learning about it myself, but I think I’ll use it in a production app because the worst that could happen is that I have to rip it out and put back some boilerplate code (i.e. initWithCoder: and encodeWithCoder: ), which is painfully boring to write.

UPDATE:

Depending on your requirements, you may want to maintain a ‘one pointer per id’ policy in your data object graph.  That means, update an existing object rather than create a new one with a new objectID, but ultimately another memory address.  What?  Basically instead of creating a new MTLModel subclass, you need a method which has the signature:

- (BOOL)updateWithJSON:(NSDictionary*)json error:(NSError**)error;

This is how I implemented that, really cool feature on Mantle that is about merging keys from other models:

- (BOOL)updateWithJSON:(NSDictionary*)json error:(NSError**)error
{
    // create a temporary object then merge it by its JSON keys
    PMMantleModel *model = [MTLJSONAdapter modelOfClass:[self class]
                                     fromJSONDictionary:json
                                                  error:error];

    if (*error != nil) {
        return NO;
    }

    NSArray *keysOfJSONProperties = [[[self class] JSONKeyPathsByPropertyKey] allKeys];

    for (id key in keysOfJSONProperties) {
        [self mergeValueForKey:key fromModel: model];
    }
    return YES;
}

I must say, I’m really quite impressed with how elegant Mantle is. So straightforward and yet so powerful.

UPDATE 2:

There were a few things that always left me a bit unsure with the Mantle framework and that was its handling of primitive types. Well, I answered some of my own questions today and wanted to share with you. I’ll just copy-paste some code and let you see for yourself how it behaves.

Say I have a model that is using only primitives:

#import "Mantle.h"

@interface HSModel : MTLModel

@property (nonatomic, assign) BOOL enabled;
@property (nonatomic, assign) NSUInteger index;
@property (nonatomic, assign) CGFloat z;
@property (nonatomic, assign) CGPoint position;

@end

Then let’s test to see how we can work with such a model. I wrote a Unit Test. Because I wish I wrote more unit tests. 😀 (I’m using Xcode 5)

/* Unit tests are executed in Alphabetical Order */
- (void)test001_testPrimitives
{
    NSError *error = nil;
    
    // I provide NSNumbers and NSValues for my primitive BOOL,
    // CGFloat, CGPoint even and you see that Mantle knows what to do!
    NSDictionary *modelDict = @{@"enabled" : @YES,
                                @"index" : @1,
                                @"z" : @3.4f,
                                @"position" : [NSValue valueWithCGPoint:(CGPoint){20.f, 20.f}]
                                };
    
    HSModel *testModel = [[HSModel alloc] initWithDictionary: modelDict
                                                       error: &error];
    
    XCTAssertNil(error, @"Should have created itself nicely");
    XCTAssertTrue(testModel.enabled == YES, @"Couldn't create it properly");
    XCTAssertTrue(testModel.index == 1, @"Couldn't create it properly");
    XCTAssertTrue(testModel.z == 3.4f, @"Couldn't create it properly");
    XCTAssertTrue(CGPointEqualToPoint(testModel.position, (CGPoint){20.f, 20.f}), @"Couldn't create it properly");
    
}

- (void)test002_testSerializationOfPrimitives
{
    NSError *error = nil;
    
    NSDictionary *modelDict = @{@"enabled" : @YES,
                                @"index" : @1,
                                @"z" : @3.4f,
                                @"position" : [NSValue valueWithCGPoint:(CGPoint){20.f, 20.f}]
                                };
    
    HSModel *testModel = [[HSModel alloc] initWithDictionary: modelDict
                                                       error: &error];

    // Here we see that it also works fine with Serialization / Deserialization
    NSData *modelData = [NSKeyedArchiver archivedDataWithRootObject:testModel];
    XCTAssertNotNil(modelData, @"Serialization failed");
    
    id result = [NSKeyedUnarchiver unarchiveObjectWithData:modelData];
    testModel = (HSModel*)result;
    XCTAssertNotNil(testModel, @"Deserialization failed");
    
    XCTAssertTrue(testModel.enabled == YES, @"Couldn't create it properly");
    XCTAssertTrue(testModel.index == 1, @"Couldn't create it properly");
    XCTAssertTrue(testModel.z == 3.4f, @"Couldn't create it properly");
    XCTAssertTrue(CGPointEqualToPoint(testModel.position, (CGPoint){20.f, 20.f}), @"Couldn't create it properly");
    
}

All of these tests pass. Which means you can read the tests above to see what the expected behaviour is and that it is that.

What about JSON? Well, let’s improve upon our Data Model:

#import "Mantle.h"

@interface HSModel : MTLModel <MTLJSONSerializing>   /* Support this protocol */

@property (nonatomic, assign) BOOL enabled;
@property (nonatomic, assign) NSUInteger index;
@property (nonatomic, assign) CGFloat z;
@property (nonatomic, assign) CGPoint position;

@end

and now we need to add a bit to our otherwise empty implementation up until now:

#import "HSModel.h"

@implementation HSModel

+ (NSDictionary*)JSONKeyPathsByPropertyKey
{
    return @{@"enabled": @"on",  // a BOOL in JSON
             @"index" : @"id",  // a NSNumber in JSON
             @"z" : @"zPos",  // a NSNumber in JSON
             @"position" : @"xyPosString"};  // assume I get a point from my JSON response but it's in a string format
}

// Not even sure if I need this.  Actually I don't.  I believe if the JSON returned it as a string I would  need this.
+ (NSValueTransformer*)enabledJSONTransformer
{
    return [NSValueTransformer valueTransformerForName:MTLBooleanValueTransformerName];
}

// this I need however because otherwise Mantle won't know what to do with the string that should become a CGPoint.
// notice I return an NSValue and also assume that when the CGPoint is converted to a string, it is already a NSValue
+ (NSValueTransformer*)positionJSONTransformer
{
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *positionString) {
        
        CGPoint point = CGPointFromString(positionString);
        return [NSValue valueWithCGPoint:point];
        
    } reverseBlock:^(NSValue *position) {
        
        return NSStringFromCGPoint(position.CGPointValue);
        
    }];
}

@end

What, you don’t believe me? You don’t have to. You do have to believe a passing unit test however. Well, at least this one:

- (void)test003_testJSON
{
    // make some pretend json coming from somewhere
    NSDictionary *json = @{@"on" : @YES,
                           @"id" : @3,
                           @"zPos" : @7.4f,
                           @"xyPosString" : NSStringFromCGPoint((CGPoint){20, 20})};
    
    NSError *error = nil;
    // This is how you create a model from JSON, not initWithDictionary: !!
    HSModel *jsonModel = [MTLJSONAdapter modelOfClass:[HSModel class] fromJSONDictionary:json error:&error];
    
    XCTAssertNil(error, @"Should have created itself nicely");
    XCTAssertNotNil(jsonModel, @"Deserialization failed");
    XCTAssertTrue(jsonModel.enabled == YES, @"Couldn't create it properly");
    XCTAssertTrue(jsonModel.index == 3, @"Couldn't create it properly");
    XCTAssertTrue(jsonModel.z == 7.4f, @"Couldn't create it properly");
    XCTAssertTrue(CGPointEqualToPoint(jsonModel.position, (CGPoint){20.f, 20.f}), @"Couldn't create it properly");
}

So there you have it. Hopefully that helps some of you!

UPDATE 3: (I should break these out into their own posts)

Up until now I’ve been quite thrilled with Mantle. Now, like any relationship, I think we just had our first fight. I’m frustrated and confused and asking myself why it had to happen this way. 😉 Anyway, I thought I’d update this with a few things I discovered:

Mantle works well with simple object graphs. But to build relationships and their inverses can be tricky. Before I get to this point I would like to point something out:

If you import a protocol that declares a property into your class, Mantle does not see it. This means, it does not appear in the automagically generated +(NSSet*)propertyKeys method and will NOT be serialized. You will have to find your own way to do this, but often it it’s a readonly property in the protocol you can just make it a readwrite in the class’ private interface.

But anyway, the other big problem I was having was that of the cyclical object graph.

Say I have:

#import "Mantle.h"
@interface HSModel : MTLModel<MTLJSONSerializing>

@property (nonatomic, assign) NSUInteger index;
@property (nonatomic, strong) NSMutableSet *children;
@property (nonatomic, weak) HSModel *parent;

- (void)addChild:(HSModel*)model;

@end

And an implementation that is:

#import "HSModel.h"

@interface HSModel()
{
    NSMutableSet *_children;
}
@end

@implementation HSModel
- (NSMutableSet*)children
{
    if (!_children) {
        _children = [NSMutableSet set];
    }
    return _children;
}

- (void)addChild:(HSModel *)model
{
    [self.children addObject:model];
    model.parent = self;
}

@end

Then any time dictionaryValue is called you will get a EXC_BAD_ACCESS crash. According to our friends over on github:

It’s seems like the issue that you are having is that MTLModel‘s default description simply invokes description of its dictionaryValue. However, if you have a cyclic reference, that means that you’ll run into a stack overflow due to the loop in your model graph. (The children set in the dictionary will invoke description on its elements, in turn invoking description on their parents :boom:)

The recommended approach is to override -description, -isEqual: and hash if your model (directly or transitively) contains cyclic references.

Recommended perhaps. But I like the description method. It is very descriptive. 😀 I want to know about the properties in my model. I also don’t want to have to override all of that for each subclass. So I took another approach. And that is to have another Baseclass that subclasses from MTLModel:

@interface HSModel : MTLModel
+ (NSSet*)propertyKeysToExcludeInDictionaryValue;
@end

then

#import "HSModel.h"
#import <objc/runtime.h>

// Used to cache the reflection performed in +propertyKeys.
static void *HSModelCachedPropertyKeysKey = &HSModelCachedPropertyKeysKey;

@implementation HSModel

/** 
 your subclass should provide a set of the keys that establish the relationship to their parent.  
 If you don't exclude these, you get cyclical object graph references and the code will crash

 Example Implementation:

 + (NSSet*)propertyKeysToExcludeInDictionaryValue
 {
    NSMutableSet *keys = [[super propertyKeysToExcludeInDictionaryValue] mutableCopy];
    [keys addObject: @"parentProperty"];
    return [keys copy];  // immutable
 }

 */
+ (NSSet*)propertyKeysToExcludeInDictionaryValue
{
    return [NSSet set];
}

// this is required to ensure we don't have cyclical references when including the parent variable.  
+ (NSSet *)propertyKeys {
    NSSet *cachedKeys = objc_getAssociatedObject(self, HSModelCachedPropertyKeysKey);
    if (cachedKeys != nil) return cachedKeys;

    NSMutableSet *keys = [NSMutableSet setWithSet:[super propertyKeys]];

    NSSet *exclusionKeys = [self propertyKeysToExcludeInDictionaryValue];
    NSLog(@"Caching Your Property Keys");
    [exclusionKeys enumerateObjectsUsingBlock:^(NSString *propertyKey, BOOL *stop) {
        if([keys containsObject: propertyKey])
        {
            [keys removeObject: propertyKey];
        }
    }];

    // It doesn't really matter if we replace another thread's work, since we do
    // it atomically and the result should be the same.
    objc_setAssociatedObject(self, HSModelCachedPropertyKeysKey, [keys copy], OBJC_ASSOCIATION_COPY);

    return keys;
}

/// ....
@end

So you see here that all you have to do is in your subclass do a quick override with any keys that should be excluded from the dictionaryValue. This generally implies the key that is associated with an inverse (i.e child getting parent) relationship.

Now don’t forget to restore the bonds of family (the parent-child relationship):

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error
{
    self = [super initWithDictionary:dictionaryValue error:error];
    if (self) {
        // go through your associations here.  self.children was deserialized and set in super (or not, in which case this below does nothing)
        [self.children enumerateObjectsUsingBlock:^(HSModel *c, BOOL *stop) {
            c.parentProperty = self;
        }];
    }
    return self;
}

But… vanity!

I don’t know if you’ve noticed, but if you have a more complex object graph, if you call po [myObject description], you *may* get a textual mess in your console. No no no, this won’t do.

UPDATE AGAIN: I figured out this textual mess. Basically it’s calling description on your models. If you want console readable output, it may be best to implement debugDescription and have it generate some JSON, then you print that to the console. (NOTE: JSON is really strings, numbers, arrays and dictionaries. So, if you are in the habit of using NSSet as your collection object, you will want to create a NSValueTransformer when converting to/from JSON:

- (NSString*)debugDescription
{
    NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:self];
    return [NSString stringWithFormat:@"%@", jsonDict];
}

+ (NSValueTransformer*)childrenJSONTransformer
{
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSArray *nodesArray) {
        
        NSMutableSet *convertedObjects = [NSMutableSet set];
        
        for (id element in nodesArray) {
            NSError *error = nil;
            HSModel *childNode = [MTLJSONAdapter modelOfClass:[HSModel class]
                                               fromJSONDictionary:element
                                                            error:&error];
            
            [convertedObjects addObject:childNode];
        }
        
        return convertedObjects;
        
    } reverseBlock:^(NSMutableSet *mutableChildren) {
        
        NSArray *allChildren = mutableChildren.allObjects;
        NSMutableArray *convertedChildren = [NSMutableArray arrayWithCapacity:allChildren.count];
        
        for (HSModel *node in allChildren) {
            NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:node];
            [convertedChildren addObject:jsonDict];
        }
        
        return convertedChildren;
        
    }];
}

There you go. Starting to take the unwieldy parts out of the Framework and most importantly, share knowledge. Please let me know if I’ve really messed something up or you have some theoretical issues with my blacksmith’s approach to programming. 😀 I like Mantle because despite some aspects of it not being fully documented, the code is written so elegantly you just have to make a test project and find out how it works, which, for the power it gives you, is not complicated at all.

Another Update

Have a look here