While we’re all aiting-way or-fay eopard-Lay, I’d like to share a pointer that I picked up while mugging a C library. (I have no idea what that means. It seemed witty when I wrote it.) As you know, I’m always ahead of the curve, setting the trends, framing the public discourse. Thus, I should add my 1.2 cents — the dollar is weak, and I’m a little short this month — on a hot topic discussed on the Cocoa-dev mailing list recently (in geological time, anyway): the use of the Objective-C BOOL type.
If you look in the header file /usr/include/objc/objc.h, you can see how BOOL is defined:
typedef signed char BOOL;
// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
// even if -funsigned-char is used.
#define YES (BOOL)1
#define NO (BOOL)0
A char type — e.g., char, signed char, unsigned char — is always one byte, i.e., sizeof(signed char) == 1, whereas in most implementations an int type is more than one byte. A byte standardly consists of 8 bits, or 12 nibbles. What happens to the extra bits if you convert an int into a BOOL? According to the wacky rules of C type conversion, the result is implementation-dependent. Many implementations simply throw away the highest bits. (Other implementations recycle them into information superhighway speed bumps.) As a consequence, it’s possible that myIntVar != 0 && (BOOL)myIntVar == NO.
Usually we don’t have to worry about this, because ‘boolean’ operators in C, such as == and !, always return 1 or 0. When we use bitwise operators, on the other hand, the problem does come into play. Suppose, for example, that we’re testing whether the option key is down. The method -[NSEvent modifierFlags] returns a bit field indicating the modifier keys that are pressed, and bit masks can be used to test for specific keys. Consider the following code; there are situations where doSomethingAfterEvent: does something, yet doSomethingElseAfterEvent: does nothing.
-(void) doSomethingAfterEvent:(NSEvent *)anEvent
{
if (anEvent)
{
if ([anEvent modifierFlags] & NSAlternateKeyMask)
{
[self doSomething];
}
}
}
-(void) doSomethingElseAfterEvent:(NSEvent *)anEvent
{
if (anEvent)
{
BOOL shouldDoSomethingElse = [anEvent modifierFlags] & NSAlternateKeyMask;
if (shouldDoSomethingElse)
{
[self doSomethingElse];
}
}
}
It has been suggested on the mailing list that the type conversion could be handled by
BOOL shouldDoSomethingElse = !!([anEvent modifierFlags] & NSAlternateKeyMask);
or
BOOL shouldDoSomethingElse = ([anEvent modifierFlags] & NSAlternateKeyMask) != 0;
However, these approaches would only work for single-bit masks. What if we wanted to test both the option key and the shift key?
The point I wish to make here actually has little to do with the BOOL type. (Say what?!?) Bitwise operators are not boolean operators. A boolean operator only returns 1 or 0. A bitwise operator, in contrast, can return any bit field. The proper way to handle a bitmask is to test whether the resulting bit field has the desired value:
unsigned int myMask = NSAlternateKeyMask | NSShiftKeyMask;
BOOL isMyKeyComboPressed = ([anEvent modifierFlags] & myMask) == myMask;
Yes, I know Robot Chicken already covered this subject a ha-while ago. I didn’t really care for it.