On NSNull as an anti-pattern

All this talk about type-safe collections may leave you thinking: but what about NSNull? Let’s say you have an array that only accepts objects conforming to MyProtocol. You can’t add +[NSNull null] to it, because it doesn’t implement the protocol. So haven’t I just broken mutable arrays?

Let’s be clear: NSNull is a nasty hack. The original inventors of Foundation wanted to provide a variadic initialiser and factory for collection classes, but rather than doing +[NSArray arrayWithCount: (unsigned)count objects: (id)firstObject, ...] they created +[NSArray arrayWithObjects: (id)firstObject, ...]. That meant they needed a special value to flag the end of the list, and they chose nil. That meant you couldn’t put nil into an array, because such an array could not be constructed using the +arrayWithObjects: style. Therefore they decided to provide a new “nothing” placeholder, and created NSNull.

NSNull makes client code warty. If you were permitted to put nil into a collection, you could just do this:

for (id <MyProtocol>foo in myFoos) {
  [foo doSomethingInteresting];
}

but if you use NSNull, you get to write this (or a close variant):

for (id <MyProtocol>foo in myFoos) {
  if ([foo conformsToProtocol: @protocol(MyProtocol)]) {
    [foo doSomethingInteresting];
  }
}

Nasty. You’ve actually got to perform the test that the protocol conformance is supposed to address, or something that gets you the same outcome.

I’d prefer to use a different pattern, common in languages where nil or its equivalent cannot be messaged, known as the Null Object Pattern. To be clear, all you do is implement a class that conforms to the protocol (or extends the superclass, if that’s what you’re up to) but doesn’t do anything interesting. If it’s interrogated for data, it just returns 0, NO or whatever is relevant. If it’s asked to do work, it just returns. In short, it can be used in the same way as a real instance but does nothing, just as a placeholder ought to behave. So we might do this:

@interface NullMyProtocolConformer: NSObject <MyProtocol> { }
@end

@implementation NullMyProtocolConformer

- (void)doSomethingInteresting { }

@end

Now we can go back to the first version of our loop iteration, and keep our type-conformance tests in our collection classes. Anywhere you might want to put NSNull, you just stuff an instance of NullMyProtocolConformer.

About Graham

I make it faster and easier for you to create high-quality code.
This entry was posted in code-level, iPad, iPhone, Mac. Bookmark the permalink.

7 Responses to On NSNull as an anti-pattern

  1. The preferred pattern in question is nore commonly known as “the Null Object pattern” than just “the Null pattern”:

    http://hillside.net/europlop/HillsideEurope/Papers/EuroPLoP2002/2002_Henney_NullObject.pdf

  2. Graham says:

    Thanks Kevlin, I’ve updated the post to reflect that.

  3. mdhughes says:

    Objective-C already does nothing on nil, it’s just NSNull that behaves badly. An easier solution is to make a helper function/macro that converts [NSNull null] into nil.

    #define NOTNULL(__x__) ((__x__) != [NSNull null] ?: nil)

    for (id foo in myFoos) {
    [NOTNULL(foo) doSomethingInteresting];
    }

  4. Would it be possible to implement the null object pattern be adding a category to NSNull which handles message forwarding?

  5. Graham says:

    @mdhughes: that’s a nice succinct way to write the test.

    @Benedict: not in a straightforward fashion. If you take the example of type safe collections, then a null object that behaved just like nil could not be used. It would answer NO to any -conformsToProtocol: or -isKindOfClass: tests so would raise exceptions on being inserted into the collection. What you’d need is some kind of generic Null Object generator, so you could create NSNull <NSCopying> or NSNull : NSString, for instance. The ObjC language doesn’t support that.

  6. Jon H says:

    Actually, doesn’t NSNull date back to EONull from EOF in the mid-90s? The need for that would be to represent NULLs brought back from the database as objects, and they needed them to be smarter than just nil pointers.

  7. Graham says:

    Jon, I don’t know which versions of EOF you’ve used, so sorry if I’m teaching you to suck eggs. In EOF 2.0, the behaviour of the framework was changed so that NULL in the database was seen as nil by EOF clients. This was specifically to avoid the issue I’ve described with NSNull – users of EOF 1.x had to write explicit tests for [EONull null] in their client code.

Comments are closed.