A Tiny Bit on Objective-C BOOLs

Note: This entire discussion applies only to the 32-bit run-time. In the 64-bit run-time BOOL is actually _Bool and is a real type. :D

It doesn't take very long with Objective-C to start using BOOLs. There are already many awesome discussions out there on their subtle, obnoxious behavior, with the following being two of the best:

There are specific issues that even appear within Apple's code that have not been widely discussed (as far as a quick web search is concerned) so here's a quick digest on two specific hazards dealing with bit fields.

Signed Char

For historical reasons BOOLs are typedef'd to signed chars, yes signed! This is super important because they are not native types; they are simply a typedef. That means the compiler can't really help ensure they behave anything differently than a signed char. Whoops!  That means you can put the value 7 in a BOOL. What does it mean? Well, read on!

1-Bit BOOLs Are Never YES

In C an n-bit signed integer will take on any value in the range [-2n-1, 2n-1-1].

An 8-bit signed integer then is anywhere in the range [-128, 127] and a 16-bit is in [-32768, 32767].

This is something most C programmers are more than familiar with. But what about a signed 1-bit integer? Given the formula above, it must be in the range

[-21-1, 21-1-1] = [-20, 20-1] = [-1, 0].

It can only be -1 or 0! It can not be the value 1 which means that it cannot be YES. This might not seem so bad until you look at a common pattern, bit fields.

struct {
  ...
  BOOL enabled:1;
} flags;

A BOOL semantic only needs one bit, since it only takes on values, so we often pack them into bit-field as shown. The only problem is that this flag can never be YES.

flags.enabled = YES;

if (flags.enabled == YES) {
  // Never going to get in here!
}

Total comedy, right? Of course there are two solutions. One would be to never compare to YES (something you should never never do anyway).

flags.enabled = YES;

if (flags.enabled) {
  // We'll make it here!
}

The above solution is absolutely correct and is exactly what all callers should be doing. But there is another solution that can be implemented at the same time and gives silly callers a little bit of leeway: don't make 1-bit fields signed.

struct {
  ...
  unsigned int enabled:1;
} flags;

You can use int or char or whatever integral type you want. It doesn't matter since you are only using 1-bit, but whatever you do, make sure it's unsigned! :D

You Need a Not-Not

So now that callers are protected it would seem as if the world has been made safe. Unfortunately the use of "bits" is still dangerous and feet will still be shot by the person they are attached to.

flags.enabled = value & 0x2;

if (flags.enabled) {
  // Did we make it here?
}

At first glance this should seem very natural. We want to know if one of the bits is set so we use a bit mask and store the result with an and. Sadly, that masking operation is going to return 0 or 2 both of which will be stored as 0 in the enabled field. Yay for integer truncation!

Worse yet, that means that storing any even integer into enabled will be NO and any odd integer will be YES. That's an API to tell your grandchildren about!

UIScrollView *scrollView = [[UIScrollView alloc] init];
int n = ...;
[scrollView setPagingEnabled:n];

if (scrollView.pagingEnabled) {
  // Did we make it here?
}

This is a real problem. The if-statement above will evaluate to true only if n was odd. So we need a healthy way to convert integers into BOOLs. We can't use a cast

UIScrollView *scrollView = [[UIScrollView alloc] init];
int n = ...;
[scrollView setPagingEnabled:(BOOL)n];

if (scrollView.pagingEnabled) {
  // Did we make it here?
}

because it is still literally, and now explicitly, a truncation of the bits in n. Time for a not-not!

UIScrollView *scrollView = [[UIScrollView alloc] init];
int n = ...;
[scrollView setPagingEnabled:!!n];

if (scrollView.pagingEnabled) {
  // We're in here as long as n != NO.
}

We could have also used an expression like (n > 0) which is just as valid. The real troublesome bit is that we actually have to do this. Why doesn't the setPagingEnabled: method do this for us?! Well, imagine that every time you wrote a method that takes a BOOL that you had to not-not it. Oy! It'd be time to switch languages at that point.

So, BOOLs have a secret, implicit contract that anyone using them enforce that they contain no value other than 0 or 1. Yes, that is on you the developer. Neither the frameworks nor the compiler will help you. Some times to use Objective-C safely you really have to know too much a lot.