Rixstep
 About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Home » Learning Curve » Developers Workshop

NSDefensiveCoding

There used to be no other way.


Get It

Try It

Start a new project in Xcode. Make it a 'Cocoa document based application'. Name it whatever you want. Build it and run it as is. Works great, what?

Let's add logic to it. Let's make a simple no code text editor. Or nearly no code.

This is the code we're interested in to start with. It's straight out of the Xcode template.

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
    // Insert code here to write your document to data of the specified type.
    // If the given outError != NULL, ensure that you set *outError when returning nil.

    // You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:,
    // or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.

    // For applications targeted for Panther or earlier systems,
    // you should use the deprecated API -dataRepresentationOfType:.
    // In this case you can also choose to override -fileWrapperRepresentationOfType:
    // or -writeToFile:ofType: instead.

    if ( outError != NULL ) {
        *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
    }
    return nil;
}

What's really interesting is the snippet they provide. Note as well the note 'ensure that you set *outError when returning nil'.

Fishy. But let's build a little functionality. We do this by opening our NIB, filling the content view with a text view, connecting an instance variable to the text view, and then adding a bit of code to the above snippet.

And let's keep it simple here. Let's assume everyone behaves and only types in 'Mac OS Roman' ASCII. And just for fun let's forget this 'outError' stuff. We don't need that, right?

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
    return [[textView string] dataUsingEncoding:NSMacOSRomanStringEncoding];
}

OK so let's run the sucker and try to save a file. And for fun let's put in a single character in the file. This one.

Paste it in the application window, hit ⌘S, give it a name, click 'OK'. Fasten your seat belt.

What We Forgot

It has to do with what we forgot.

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
    // If the given outError != NULL, ensure that you set *outError when returning nil.

    if ( outError != NULL ) {
        *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
    }
}

It's the admonition you have to 'set' outError. And certainly this is strange.

By testing various scenarios and tossing back various 'weird' values it's possible to determine a number of things.

  • The NSError variable passed in is a stack variable with a garbage value.
  • The calling code doesn't safeguard outError - as in 'NSError *outError = nil'.
  • The calling code knows how to deal with a nil *outError - a default alert panel and message.
  • The calling code assumes the value of *outError is valid even though it couldn't bother initialising it.

That's not defensive coding. It's not even Comp Sci 102.

See Also
Learning Curve: NCE - The OS X No Code Editor 1
Learning Curve: NCE - The OS X No Code Editor 2
Learning Curve: NCE - The OS X No Code Editor 3

About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Copyright © Rixstep. All rights reserved.