Dustin Bachrach

mobile engineer / developer platform enthusiast / dog lover

An Annotated NSArray

At Airstrip, we have lots of code built up around handling JSON model responses. We have a generic transformation process that can take JSON and instantiate concrete model objects. One of the major difficulties, though, is handling array relationships. Imagine the following model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface Person : Model
@property (strong) NSString* name;
@property (strong) Address* address;
@property (strong) NSArray* phoneNumbers;
@end

@interface Address : Model
@property (strong) NSString* number;
@property (strong) NSString* street;
@property (strong) NSString* zip;
@end

@interface TelephoneNumber : Model
@property (strong) NSString* name;
@property (strong) NSString* number;
@end

Our app would receive the following JSON for a call to a specific endpoint that we know describes a Person.

1
2
3
4
5
6
7
8
{
    "name": "Dustin",
    "address": { "number": 123, "street": "Main St", "zip": 99999 },
    "phoneNumbers": [
        { "name": "mobile", "number": "555-555-5555" },
        { "name": "home", "number": "555-555-5556" }
    ]
}

When we parse this JSON and try to produce a Person model, we can easily fill in the name property. The Objective-C property is declared to be an NSString, and the JSON data contains the string “Dustin”. So we can just set the person’s concrete name property to “Dustin”. We know that the address property aligns with the address JSON attribute, so we can construct an Address instance and create it with this JSON:

1
{ "number": 123, "street": "Main St", "zip": 99999 }

Just like we could set the Person’s name to “Dustin”, we can set this new Address object’s number to be “123”, its street to be “Main St”, and its zip to be “99999”.

Getting back to parsing the JSON for the Person, it’s tough when we evaluate the phoneNumbers property. What we want to happen is to construct two TelephoneNumber classes, create an array that points to them, and then assign that to the concrete phoneNumbers property. But our JSON transformation system looks at the phoneNumbers property and sees the type NSArray.

We need to tell the transformation system extra information about what belongs inside the NSArray. We need an annotated NSArray.

The unfortunate reality is that it’s just not possible (if it is possible, I would love to be wrong, so tweet me). I’ll describe a bunch of alternatives to try and solve this problem, and give you my suggestions.

The unfortunate IBOutletCollection

I’ll start off with a non-solution. You may be aware of the IBOutletCollection macro, which you can use to annotate a property:

1
@property (weak) IBOutletCollection(UILabel) NSArray* labels;

In Interface Builder you can then hook up multiple label outlets to the labels property.

If you were hoping that this could magically be added to our model and used by our transformation system:

1
@property (strong) IBOutletCollection(TelephoneNumber) NSArray* phoneNumbers;

You would be sad to look up the definition of IBOutletCollection:

1
#define IBOutletCollection(ClassName)

Yeah, it gets removed and does us no good.

Protocols

Objective C Generics and JSONModel both take an interesting approach. They use protocols to annotate the contents of an NSArray rather than to describe behavior of the NSArray itself. Consider this example:

1
2
3
@interface Person : Model
@property (strong) NSArray<TelephoneNumber>* phoneNumbers;
@end

You then need to create a protocol with the same name as your class:

1
2
@protocol TelephoneNumber
@end

This absolutely works, and it’s a really clever workaround, but it’s very misleading. For this problem, where we are more concerned with annotation (like JSONModel), and less concerned with strict generics (like Objective C Generics), the syntax implies a lot more than we really want. We aren’t adding strict generics, but it looks like we are. You also have to create a protocol for each of your model classes, which is where the abstraction shows its leaks. I can’t entirely rule this option out because it does function properly. I’m just hesitant of what this might do for a large code base.

Getting your server involved

We could stop trying to annotate our Objective-C code, and annotate the JSON instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
    "_model": "Person",
    "name": "Dustin",
    "address": {
        "_model": "Address",
        "number": 123,
        "street": "Main St",
        "zip": 99999
    },
    "phoneNumbers": [
        {
            "_model": "TelephoneNumber",
            "name": "mobile",
            "number": "555-555-5555"
        },
        {
            "_model": "TelephoneNumber",
            "name": "home",
            "number": "555-555-5556"
        }
    ]
}

By pushing the burden to the server, we’ve now worked around Objective-C’s limitation and tightly coupled the server with the client’s class names. If your Android and iOS clients both use the same JSON endpoints, then now you have to ignore the _model field in Android, converge your Android and iOS class names, or add another JSON field: _modelAndroid.

If you rev your client, you may need to rev your server. Can your new server communicate with old clients? To be fair, there’s already tight coupling between server JSON and client concrete models. Maybe this method is not concerning to you. Maybe it’s a real issue.

Typed NSArrays

I went exploring down this path a bit. Basically:

1
2
3
4
5
6
@interface TelephoneNumbersArray : NSArray
@end

@interface Person : Model
@property (strong) TelephoneNumbersArray* phoneNumbers;
@end

This looks somewhat appealing. Just subclass NSArray and create your own typed array class. Sure, now every model class needs to create its own array, but even more concerning is how difficult subclassing NSArray is. It does not do what you think it does. Subclassed NSArrays do not provide the same implementation as [NSArray array]. You have to go implement your own array storage and conform to the NSArray interface. This is something I didn’t want to mess around with at the time, and my investigations ended there. Perhaps you can make this work. If you have, get in touch.

The oh-god-no option

I showed this to my boss, and he said he wanted to throw up. It’s truly terrible, and don’t use it, but I like the hack.

1
@property (getter = TelephoneNumber) NSArray* phoneNumbers;

Objective-C limits exactly what can be associated with a property. This guide describes it in detail, but a property has a name and a set of attributes (readonly/readwrite, atomicity, memory management, raw type, and getter/setters). Most of those are unusable booleans or enums, but getter/setter are strings. Rather than using the getter/setter for its normal use, we can stuff annotations in there.

At runtime we can inspect them via property_getAttributes() and then treat the getter as the NSArray’s annotated type rather than a getter.

I repeat, don’t do this.

Source rewriting

ObjectiveCAnnotate is a code generation tool that lets you add source-level annotations to your code that will be translated pre-compile-time.

For example:

1
2
//@properties (assign)
NSString *aVariable;

Will be transformed into:

1
@property (assign) NSString *aVariable;

ObjectiveCAnnotate doesn’t have built-in support for what we want, annotated NSArrays, but you could easily add support for it with their custom annotations.

I wasn’t huge on having to run a pre-compilation step in our build just to get this feature. And adding something like this certainly increases the complexity of your code, making a new-hire’s life more complicated.

Implicit property names

This is actually a reasonable solution, which requires no special annotation. Just name your properties as the plural name of a class. The transformation system can get the property name, singularize it, optionally prepend a namespace prefix, and there’s your annotated NSArray’s type.

In our example where the model class was named TelephoneNumber, we could change from:

1
@property (strong) NSArray* phoneNumbers;

to

1
@property (strong) NSArray* telephoneNumbers;

We could have instead changed our model class to be named PhoneNumber to match the property.

For most code you might not even need to rename. You likely are already naming your properties the same as your classes.

This is not a perfect solution. We are forming a tight coupling between our server and client because the server property names need to match the concrete class names.

It’s also reasonable to have a situation where you have to have different names:

1
2
3
4
@interface NationalOfficial : Model
@property (strong) NSArray* privateTelephoneNumbers;
@property (strong) NSArray* secureTelephoneNumbers;
@end

Here our NationalOfficial will have an array of private phone numbers and a separate array of secure phone numbers. If we require the property name to be the same as the class name, they can’t both point to TelephoneNumber, so we would only be able to have one telephone number array.

Explicit property transformer

Finally, we can give up our lofty goal of implicitly deducing this information and rely exclusively on explicitly listing property types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface Model
- (NSDictionary*)transformableProperties;
@end

@interface PropertyType
+ (instancetype)typeOf:(Class)class;
+ (instancetype)arrayTypeOf:(Class)class;
+ (instancetype)integerType;
// ... all other primitive type constructors
@end

@implementation Person

- (NSDictionary*)transformableProperties
{
    return @{ "name": [PropertyType typeOf:[NSString class]],
              "address": [PropertyType typeOf:[Address class]],
              "phoneNumbers": [PropertyType arrayTypeOf:[TelephoneNumber class] };
}

@end

Each model object needs to override the -transformableProperties method and return a dictionary of property names to types. The PropertyType would be a class which can represent primitive types, class types, and annotated collection types.

The obvious downside is that we are having to repeat ourselves in our interface declaration and implementation. But we do get very explicit behavior that can be read by everyone of your developers (new or old), and it makes sense.

My preference

My personal view on this is to write as little code as possible. That’s why I set out on this whole process. Our current solution was the explicit property transformer, and I thought it was verbose, redundant, and difficult to maintain.

I think the right balance is in a mix of implicit and explicit. For most scenarios the implicit property names method works great. Once you understand it, it’s clean and beautiful. And if you ever need to support situations where your property names cannot match, use -transformableProperties. Any property names returned from there are used as specified and all others will be inferred.

Thanks for reading, and I hope this recap gives you enough information to make an informed decision. Please feel free to get in touch, especially if you have a solution I’m missing.