0

I'm using a selector to change a View item's visibility

[target performSelector:@selector(setVisible:) withObject:[NSNumber numberWithBool:YES] afterDelay:delay];

and my function in the view is:

- (void)setVisible:(BOOL)isVisible_
{
    if (isVisible_)
    {
        ...
    }
    else
    {
       ...
    }
}

And this code works different for iPhone and iPad. In iPhone isVisible_ is always NO. in iPad it works correct. When I changed my function to get an id instead of BOOL, and then cast it to BOOL myself, it worked correctly on both devices. But what is the reason for this behaviour?

Adam Richardson
  • 2,518
  • 1
  • 27
  • 31
  • replace `withObject:[NSNumber numberWithBool:YES]` with `withObject:YES`. – Anoop Vaidya Dec 08 '14 at 12:17
  • thank you, I already changed it. Question is "Why is it behaving differently" though. I understand why it didn't work on iphone, I don't understand why it was working on ipad. – Burcu Arabaci Dec 08 '14 at 12:41
  • 1
    performSelector can only pass objects and it doesn't unwrap NSNumber's automatically. – pronebird Dec 08 '14 at 12:50
  • 1
    @AnoopVaidya: Congratulation. That's an unexpected third method that doesn't work at all. Read the documentation for performSelector. – gnasher729 Dec 08 '14 at 13:10

2 Answers2

5

Your code is totally and utterly broken.

You are passing an NSNumber to a method that expects a BOOL. That's rubbish. Why would you ever expect that to work? Taking an id and casting to BOOL is just as rubbish. You can't cast an object to BOOL and expect something meaningful.

You need a method with an NSNumber* parameter, and then use boolValue.

Or get rid of that performSelector crap and use a block instead.

Look at GCD and especially dispatch_after for a much much much cleaner way to handle this situation.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • That wasn't my code, and I edited it as I already said. I just wanted to know what might be the reason behind this two different results. – Burcu Arabaci Dec 08 '14 at 12:14
  • it is a lengthy and hacky code dude.... int to number, then number to int without any specific reason. why not propose what I posted as comment above? – Anoop Vaidya Dec 08 '14 at 12:36
  • Broken code can produce whatever results it wants. It can produce different results just because it feels like it. It's called "undefined behaviour" which means anything could happen. Next OS version it might crash, and that wouldn't be a surprise. – gnasher729 Dec 08 '14 at 13:14
  • as a support to this answer; there is an example of delayed perform in this answer. [link text](http://stackoverflow.com/questions/15413014/objective-c-delay-action-with-blocks) – meth Dec 08 '14 at 13:15
  • She apparently had no clue that unwrapping does not happen automatically. Too bad analyzer does not even warn about methods with primitive types being called with `performSelector`. – pronebird Dec 08 '14 at 13:27
3

That should be:

- (void)setVisible:(NSNumber *)isVisible
{
    BOOL b = [isVisible boolValue];
    if (b)
    {
        ...
    }
    else
    {
       ...
    }
}
Droppy
  • 9,691
  • 1
  • 20
  • 27
  • Thank you, but I already solved it with a similar approach. I meant to ask why it was working on iPad? – Burcu Arabaci Dec 08 '14 at 12:20
  • it is a lengthy and hacky code dude.... int to number, then number to int without any specific reason. why not propose what I posted as comment above? – Anoop Vaidya Dec 08 '14 at 12:35
  • @Droppy it was working on iPad. So my question was "Why it was working on iPad" if you don't know, you don't have to answer. – Burcu Arabaci Dec 08 '14 at 12:44
  • @AnoopVaidya Because the `withObject` parameter is of type `id` and it's unclear what ARC will do with the object; for example will it call `retain` on that "object"? If so that will crash. – Droppy Dec 08 '14 at 12:51
  • @BurcuArabaci That would be undefined behaviour. The code is broken and it shouldn't work anywhere. I bet it doesn't work anywhere passing `NO`. – Droppy Dec 08 '14 at 12:52
  • @BurcuArabaci instead of `BOOL` that you expected, method received a pointer to `NSNumber`. Pointer or address in memory is an integer (can be 32 or 64bit long) and `BOOL` in Objective-C is `CHAR` and is 1 byte long. Which means that after casting address to `BOOL`, depending on lower byte in it, you may get different behavior. So if lower byte is 0 then you get one behavior, if it's 1 then another. There is an interesting article about all kinds of BOOLs - http://nshipster.com/bool/ – pronebird Dec 08 '14 at 12:56
  • Thanks @Andy, this looks like the answer. Droppy, you may be right, I didn't see if it was working with NO. I agree this code is totally wrong, I just wondered how it looked like working in the first place. – Burcu Arabaci Dec 08 '14 at 13:14
  • 1
    @Andy: It's worse. On 64 bit, BOOL is bool and has an unspecified size but automatically converts anything that isn't zero to YES. So if you try to store 256 into a BOOL, you get NO on 32 bit and YES on 64 bit. – gnasher729 Dec 08 '14 at 13:16
  • @gnasher729 wow, thanks. This explains much. Could you add this as answer so I can accept it? – Burcu Arabaci Dec 08 '14 at 13:18
  • @gnasher729 Apple documentation says it's 1byte for 32 and 64 platforms https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/Major64-BitChanges/Major64-BitChanges.html – pronebird Dec 08 '14 at 13:20
  • 1
    @BurcuArabaci good article with pictures what happens when casting http://www.bignerdranch.com/blog/bools-sharp-corners/ – pronebird Dec 08 '14 at 13:23