Ky -
Ky -

Reputation: 32173

Why is my YES BOOL turned into a true bool by negator?

I have the following code:

    // ...

    [self performSelectorInBackground:@selector(mySel:) withObject:@YES];
}

// ...

- (void) mySel:(BOOL)suppressUserAlerts
{
    NSError*error;
    [obj doActionWithError:&error];
    if (!suppressUserAlerts && error) // line with breakpoint
    {
        [self alertOfError:error];
    }

    // ...

Now, even though I passed in @YES, alertOfError: is always called. So, naturally, I debugged it and added some watched expressions and breakpoints, and I found this nonsense:

A screenshot of two expressions: <code>!suppressUserAlerts = (bool) true</code> and <code>suppressUserAlerts = (BOOL) YES</code>

So, baffled, I ask: why is suppressUserAlerts YES, and !suppressUserAlerts true? And, more importantly, How do I get the value I want?

Upvotes: 3

Views: 67

Answers (3)

Julian F. Weinert
Julian F. Weinert

Reputation: 7570

@timgcarlson is right in his corrections.

Nevertheless, to answer your question:

You are actually passing an object by @YES. IF you now have the statement

if (!supressUserAlerts) {

you are actually testing for the non-existence of the object (equivalent to supresseUserAlerts == nil). The BOOL declaration is more like a cast that only effects the debugger.

So your statement will always be true because @YES creates an object that not equals nil.

EDIT

To fix your bug in a proper way:

    // ...
    [self performSelectorInBackground:@selector(mySel:) withObject:@YES];
}

- (void)mySel:(NSNumber *)suppressUserAlerts {
    NSError *error;
    [obj doActionWithError:&error];

    if (![suppressUserAlerts boolValue] && error) {
        [self alertOfError:error];
    }

    // ...

EDIT II

I think what you are seeing in the debugger is just the way it outputs the scalar boolean value (created by the ! prefix) vs. the object bool.

Upvotes: 2

Ken Thomases
Ken Thomases

Reputation: 90721

This line:

[self performSelectorInBackground:@selector(mySel:) withObject:@YES];

is passing an NSNumber instance. It is necessary to use an object rather than a scalar BOOL value here because -performSelectorInBackground:withObject: can only handle object-typed arguments (as "withObject:" suggests).

However, your method -mySel: take a BOOL parameter. Nothing in the framework will automatically unbox the NSNumber to convert it to the BOOL. Instead, your method is receiving an object pointer value but interpreting it as a BOOL.

Now, BOOL is an unsigned char or, on some architectures, a bool. So, only the low byte or low bit is examined by the "if" statement. A pointer to an NSNumber is quite likely to have a zero low byte or bit. Note that it doesn't matter what the value of the NSNumber object is. Only its address is being examined.

The debugger is probably confused. It may be doing the equivalent of "po <full 64-bit register value holding the object pointer>". So, even though your program is only examining the low byte or bit, the debugger is examining the full 64-bit value and treating it like an object pointer. Thus the debugger is misleading you as to what should happen.

As others have suggested, you can change your -mySel: method to take an NSNumber* rather than a BOOL. You will have to use the -boolValue method to examine that object's value.

However, you can also avoid this sort of issue by using Grand Central Dispatch (GCD) rather than -performSelectorInBackground:withObject:. Your code could be:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self mySel:YES];
});

There's no need to box the YES value into an object, so there's no problem with it being interpreted incorrectly in the method. And, if you did happen to have a type mismatch, the compiler can recognize it and warn you about it, which it can't do with -performSelector... methods.

Or, if you don't really need the -mySel: method except because you wanted to refer to it by a selector, you could directly inline its code into the block.

Upvotes: 1

timgcarlson
timgcarlson

Reputation: 3146

BOOL is not an object in Objective-C. One way to fix this error would be to pass the BOOL value (which is just a 0 or 1) via an NSNumber (an object).

[self performSelectorInBackground:@selector(mySel:) withObject:[NSNumber numberWithBool:YES];

- (void)mySel:(id)suppressUserAlerts {
    BOOL suppressUserAlertsBool = suppressUserAlerts.boolValue;
    // ...
}

EDIT: Note that you can still just pass @YES instead of [NSNumber numberWithBool:YES] as Cocoa will handle the conversion for you. I was just using numberWithBool to be more explicit.

Upvotes: 0

Related Questions