Nov 29 2012

An Objective-C Literal Subtlety

Update: In the time since this blog post was published, the compiler behavior demonstrated has been remedied in the version of clang accompanying Xcode 4.6. For a BOOL generateMipmaps, @(generateMipmaps) now evaluates to kCFBooleanTrue or kCFBooleanFalse.

Earlier this year, Apple introduced new syntax for Objective-C literals—perhaps you read Mark’s early coverage of it here on the blog (Part 1Part 2). This new syntax is a great improvement for Cocoa and iOS developers: suddenly our code can be much less verbose, while remaining expressive. There are some sharp edges, however. Consider @YES, which is shorthand for [NSNumber numberWithBool:YES]. There’s also @42 and so forth, but what if we want to store the value of an expression in an NSNumber? The boxed expression helps with that: @(1 + 3). We also need to use the boxed expression for variables. For example: @(answer). There’s a subtlety to this last case, however. I was taking our OpenGL class recently and was using GLKit’s excellent GLKTextureLoader to, well, load a texture:

options = @{};
tex = [GLKTextureLoader textureWithContentsOfFile:path
                                          options:options
                                            error:&error];

Then I decided I might want to generate mipmaps for my texture.

BOOL generateMipmaps = YES;
options = @{ GLKTextureLoaderGenerateMipmaps : @(generateMipmaps) };
tex = [GLKTextureLoader textureWithContentsOfFile:path
                                          options:options
                                            error:NULL];

Alas, the mipmaps were nowhere to be found. After entirely too much searching for problems loading mipmaps, I started poking at my assumptions. On a whim I tried simplifying my options dictionary:

options = @{ GLKTextureLoaderGenerateMipmaps : @YES };

Suddenly, I had mipmaps, but more importantly I learned that

@(YES) has a different result from @YES. Using po (Class)[obj class] I was able to find that @(YES) has a class of __NSCFNumber, while @YES is an __NSCFBoolean. So why is -textureWithContentsOfFile:options:error: interpreting my __NSCFNumber for that key as NO? According to the documentation, the value for the key GLKTextureLoaderGenerateMipmaps is “an NSNumber object that specifies a boolean value”. NSNumbers simply hold a scalar, right? And [[NSNumber numberWithInt:YES] boolValue] == [[NSNumber numberWithBool:YES] boolValue]. Why didn’t -textureWithContentsOfFile:options:error: agree? I reasoned that I could take advantage of Objective-C’s message passing and log all messages sent to my NSNumber instance. I pulled out dtrace and wrote a script — the subject of another blog post — and found, to my surprise, that -textureWithContentsOfFile:options:error: wasn’t sending any messages to my NSNumber! After conferring with my fellow nerds we found a probable explanation: the NSNumber‘s value is likely being checked using something like the following:

NSNumber *generateMipmaps =
    [options objectForKey:GLKTextureLoaderGenerateMipmaps];
if (generateMipmaps == kCFBooleanTrue)
{
    // generate those mipmaps
}

That would explain why no messages are sent to our number object, and why this method displays a distinction between

[NSNumber numberWithInt:YES] and [NSNumber numberWithBool:YES]. As an optimization, the latter always returns the same pointer (kCFBooleanTrue). The same is true for the NO counterpart — it always returns kCFBooleanFalse. I would argue that this method should use -boolValue to check the options dictionary values, but I would also argue that this is something the compiler should be handling when it translates the literal syntax into an object. I’ve filed radars on both of these issues this with Apple (rdar://12761147 and rdar://12761621). Enjoy the new literal syntax with care, and watch out for these sorts of pitfalls in your Objective-C work. If you’re a consumer of APIs, be aware of this slippery behavior when mixing @() with YES and NO. If you’re writing an API, the extra -boolValue won’t cost that much, and you’ll save yourself emails from confused users.

1 Comment

  1. Andrew Small

    Every time I see a comparison to some form of YES, I cringe since someone may have taken advantage (intentionally or unintentionally in your case) of the fact that any non-zero value will evaluate to true in the conditional.

    In a recent code base I saw a fair bit of

    if (b == YES){}

    and I change it to


    if (b){}

    which is more reliable.

Leave a Comment

Join the discussion. Do not worry, your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>