Simplifying Objective-C Value Objects with Mantle and the Builder Pattern

I’ve come to the conclusion that regardless of how “functional” a programming language is, the best way to handle state is with immutable value objects.

Wikipedia defines a value object as follows:

“In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object.

Value objects should be immutable: this is required for the implicit contract that two value objects created equal, should remain equal. It is also useful for value objects to be immutable, as client code cannot put the value object in an invalid state or introduce buggy behaviour after instantiation.”

I’ve found that immutable value objects are particularly well suited for passing data around when using one of the Reactive Extensions libraries. While not a direct port, I lump ReactiveCocoa into the same category, and it benefits just as much by sending Objective-C value objects on its signals/streams.

Making the Most of Mantle

We’ve talked about simplifying iOS models with Mantle here before. That earlier post points out the power of Mantle when it comes to JSON serialization. Another area where the Mantle library provides immense help is when defining value objects. There’s an excellent objc article on value objects that mentions that Mantle can be used to automate the implementation of all of the requirements of a value objects in Objective-C (which happen to be pretty onerous compared to other languages).

For an Objective-C class to be used as a value object, it needs to be immutable, support value equality, implement proper hashing, and support NSCopying. Mantle provides the latter three just by subclassing MTLModel.

Making an Objective-C Class Immutable

This is pretty simple–just define each property as being readonly:


@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, copy, readonly) NSNumber *age;
@property (nonatomic, copy, readonly) NSNumber *height;
@property (nonatomic, copy, readonly) NSNumber *weight;

The difficulty comes when you need to create one of these objects, either from scratch or based on an existing object. One common option is to create an initializer that accepts an initial value for all of the properties.


Person *person = 
    [[Person alloc] initWithName:@"Joe" 
                             age:@32 
                          height:@72 
                          weight:@185 
                           alive:YES];

This works reasonably well for classes with only a few properties, but it gets very unwieldy when there are more. And very often, especially in tests, you only care about setting one or two specific properties, but you still need to deal with a giant initializer passing in nil for all of the things you don’t care about.

This still doesn’t address the issue of wanting to “modify” a property of an immutable object–in other words, figuring out how to create a new instance based on an existing one, but with one or more properties having different values.

Using Builder Pattern

An improvement over using an initializer like this is the Builder Pattern. Inspired by Klaas Pieter’s The Builder Pattern in Objective-C, I’ve been using this pattern to construct new instances, either from scratch or based on an existing object.

Using this pattern, here’s how to instantiate the same Person object as above:


Person *person = 
    [Person makeWithBuilder:^(PersonBuilder *builder) {
        builder.name = @"Joe";
        builder.age = @32;
        builder.height = @72;
        builder.weight = @185;
        builder.alive = YES;
    }];

To create a Person specifying only the name, do this:


Person *person = 
    [Person makeWithBuilder:^(PersonBuilder *builder) {
        builder.name = @"Joe";
    }];

It’s extremely useful to be able to create minimal instances of value objects like this in test code.

In addition to the makeWithBuilder class method, I’ll also add an update instance method that allows you to create a new instance based on the original values, like so:


Person *newPerson = 
    [person update:^(PersonBuilder *builder) {
        builder.age = @33;
        builder.weight = @190;
    }];

After making the above call, there will be two distinct instances of Person. Both will have the name “Joe” and a height of 72, but different ages and weights.

Here’s what the full header and implementation files for the Person class look like:


#import <Foundation/Foundation.h>
#import "Mantle.h"

@interface PersonBuilder : MTLModel 
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSNumber *age;
@property (nonatomic, copy) NSNumber *height;
@property (nonatomic, copy) NSNumber *weight;
@property (nonatomic, getter=isAlive) BOOL alive;
@end

@interface Person : MTLModel 
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, copy, readonly) NSNumber *age;
@property (nonatomic, copy, readonly) NSNumber *height;
@property (nonatomic, copy, readonly) NSNumber *weight;
@property (nonatomic, readonly, getter=isAlive) BOOL alive;

- (instancetype)init;
- (instancetype)initWithBuilder:(PersonBuilder *)builder;
+ (instancetype)makeWithBuilder:(void (^)(PersonBuilder *))updateBlock;
- (instancetype)update:(void (^)(PersonBuilder *))updateBlock;
@end

#import "Person.h"

@implementation PersonBuilder
- (instancetype)init {
    if (self = [super init]) {
        _name = nil;
        _age = nil;
        _height = nil;
        _weight = nil;
        _alive = YES;
    }
    return self;
}
@end

@implementation Person

- (instancetype)initWithBuilder:(PersonBuilder *)builder {
    if (self = [super init]) {
        _name = builder.name;
        _age = builder.age;
        _height = builder.height;
        _weight = builder.weight;
        _alive = builder.alive;
    }
    return self;
}

- (PersonBuilder *)makeBuilder {
    PersonBuilder *builder = [PersonBuilder new];
    builder.name = _name;
    builder.age = _age;
    builder.height = _height;
    builder.weight = _weight;
    builder.alive = _alive;
    return builder;
}

- (instancetype)init {
    PersonBuilder *builder = [PersonBuilder new];
    return [self initWithBuilder:builder];
}

+ (instancetype)makeWithBuilder:(void (^)(PersonBuilder *))updateBlock {
    PersonBuilder *builder = [PersonBuilder new];
    updateBlock(builder);
    return [[Person alloc] initWithBuilder:builder];
}

- (instancetype)update:(void (^)(PersonBuilder *))updateBlock {
    PersonBuilder *builder = [self makeBuilder];
    updateBlock(builder);
    return [[Person alloc] initWithBuilder:builder];
}
@end

Simplifying with Code Generation

By using Mantle, I’ve removed a ton of boilerplate from the model definitions. But the Builder Pattern has re-introduced a lot of boilerplate back into every value object that’s defined. The models have gone from quick and easy to define and update, to painful to maintain. Now, we have to remember to modify both the builder class and the read-only class, as well as all of the initializers and copy methods in the implementation.

One great way I’ve found to ease this kind of pain is by using Active Code Generation, as described by the Pragmatic Programmers.

By using a “schema” to define your models, you can automate the generation of all of that boilerplate code. You can even make it part of the build process so changes to the schema files are automatically incorporated into the project at compile time.

Here’s an example of a definition of the Person class used earlier written in a Ruby DSL:


model "Person" do
  property "name", type: "NSString *"
  property "age", type: "NSNumber *"
  property "height", type: "NSNumber *"
  property "weight", type: "NSNumber *"
  property "alive", type: "BOOL", default: "YES", getter: "isAlive"
end

In a follow-up post, I’ll show you how to put together a Ruby script that can translate this code into the Objective-C header and implementation files shown earlier.

Summing Up

Immutable objects with no behavior and value equality are a really good way of managing state. It’s such a good idea that it’s worth figuring out how to make it as easy as possible.

In Objective-C, the Mantle library can do most of the work for you, providing value equality, hashing, and NSCopying right out of the box. Implementing some form of the Builder Pattern makes it very easy to create and use instances of the models. And code generation makes it possible to automate away the pain of a bunch of boilerplate code.

Conversation
  • Brandt says:

    I’m seeing duplicate symbols between the Builder and the Object!

  • Comments are closed.