45

Is there any way to send a BOOL in selector ?

[self performSelector:@selector(doSomething:) withObject:YES afterDelay:1.5];

Or I should use NSInvocation? Could somebody write a sample please ?

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Jim
  • 8,874
  • 16
  • 68
  • 125

14 Answers14

70

In the case that you cannot alter the target-method signature to accept a NSNumber in place of a BOOL you can use NSInvocation instead of performSelector:

MyTargetClass* myTargetObject;
BOOL myBoolValue = YES; // or NO

NSMethodSignature* signature = [[myTargetObject class] instanceMethodSignatureForSelector: @selector( myMethodTakingBool: )];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: signature];
[invocation setTarget: myTargetObject];
[invocation setSelector: @selector( myMethodTakingBool: ) ];
[invocation setArgument: &myBoolValue atIndex: 2];
[invocation invoke];
TomSwift
  • 39,369
  • 12
  • 121
  • 149
  • This is the best answer, as it does not require the target selector to be modified. HOWEVER, you should actually make sure that whatever variable you use to store the BOOL value (myBoolValue) does not fall out of scope before the invocation executes, or you may end up with unpredictable results. I recommend using either an iVar or a static const. – quickthyme Apr 24 '13 at 22:27
  • 1
    @quickthyme - not clear on what you're suggesting is the issue. 'invoke' is a synchronous call, so how would 'myBoolValue' fall out of scope during invocation? – TomSwift Apr 25 '13 at 17:06
  • I could be wrong, but my understanding is that by creating the NSInvocation object, you are essentially archiving an Obj-C message that will execute outside of this block of code. The address pointer (for myBoolVal) you are passing as the argument will fall out of scope, and that memory address will no longer be valid. – quickthyme May 14 '13 at 00:16
  • 3
    @quickthyme - I believe the call to invoke is synchronous. – TomSwift May 14 '13 at 16:19
  • 2
    Yes, but your example, while correct, assumes that 'invoke' is being called within the same block that the invocation itself gets created. In situations where 'invoke' is being called somewhere else however, (which is more often), the local 'myBoolValue' variable may no longer exist. Therefore, any address pointers referencing it are no longer trustworthy. Hence, it might be better to pass a pointer to a static var or an instance var instead. Your answer is fine. I just felt that this was an important detail worth mentioning in case it could save someone else enormous trouble later on... – quickthyme May 14 '13 at 18:51
  • 1
    @quickthyme - I see your point. If your intent is to create an invocation object and store it for use later, you need to ensure that the arguments don't live in the stack frame where you constructed the NSInvocation. – TomSwift May 15 '13 at 17:22
57

you can use NSNumber to wrap bools types:

BOOL myBool = YES;
NSNumber *passedValue = [NSNumber numberWithBool:myBool];
[self performSelector:@selector(doSomething:) withObject:passedValue afterDelay:1.5];

and in the selector, to get the bool value, you use:

BOOL value = [recievedObject boolValue];
Rohit Pradhan
  • 3,867
  • 1
  • 21
  • 29
Niv
  • 2,294
  • 5
  • 29
  • 41
  • 14
    This is fine if you are able to manipulate the signature of the target selector. If not you should use NSInvocation. – TomSwift Jan 04 '13 at 17:21
  • 1
    How bout an answer or a link to an answer suggesting an NSInvocation solution? – Morkrom Aug 13 '13 at 22:55
  • 3
    Warning: I've noticed that under certain conditions doing performSelector and passing an NSNumber for BOOL can give you ambiguous results when you later ask for that BOOL property. it seems to hold a ref to the NSNumber (or some numerical value) instead of an actual BOOL. – Matjan Mar 08 '14 at 18:02
  • NSInvocation is better – Startry Apr 05 '17 at 05:10
  • @TomSwift If you cannot change the original code, you can create a wrapper method in your own class, to call the original method. Then `performSelector` on your own object instance. – DawnSong Jan 23 '19 at 17:30
18

The simplest way is as follows:

If you have method

-(void)doSomething:(BOOL)flag

and want to performSelecor with flag=NO use

[object performSelector:@selector(doSomething:) withObject:nil];

In case of flag=YES you can send any object, for example, @YES - number from bool

 [object performSelector:@selector(doSomething:) withObject:@YES];

Note: don't use @NO ! Only nil will be interpreted as NO in your method with bool argument.

malex
  • 9,874
  • 3
  • 56
  • 77
  • I don't know wether it's a bug, I tested on `iOS12` and `iOS 8.1` on simulator, the `flag` always equal to `NO`, no matter parameter is `@YES` or `nil`. – zhongwuzw Oct 21 '18 at 08:22
  • I don't think you are correct. For the `doSomething:` method, you need to create a wrapper method, like `doSomethingObj:(NSNumber*)flag`, which call `doSomething:` to finish its work. – DawnSong Jan 23 '19 at 17:26
  • If you have a `NSInteger` parameter, you will see the argument you get is the pointer value from `performSelector:withObject:`, instead of the `[number integerValue]`. Objc won't unwrap the NSNumber value for you. – DawnSong Jan 23 '19 at 17:28
14

use dispatch_after in main thread like this:

dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (_animationDuration+1) * NSEC_PER_SEC);
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
    [self completeAnimation:NO];
});
user2142920
  • 231
  • 3
  • 5
  • Warning - do not use this if timing is crucial as this will fire about 10% later than scheduled e.g. if you schedule it for 3s from now, it will typically fire at about 3.3s. If you need more exact timing use performSelector or a Timer – RunLoop Nov 25 '19 at 16:26
6

if you want to pass a NO,and only a NO, you can put a zero instead

[self performSelector:@selector(doSomething:) withObject:0 afterDelay:1.5];

But remember, just work for zero, doesn't compile with ARC wich disallow implicit conversion

Gonzo Oin
  • 369
  • 2
  • 12
  • with arc you can use nil in this case. [self performSelector:@selector(doSomething:) withObject:nil afterDelay:1.5]; and it will behave like [self doSomething:NO]; was called after 1.5 seconds. -rrh – Richie Hyatt Mar 04 '14 at 18:07
5

Another way of doing this is to pass nil for NO and anything else for YES

[self performSelector:@selector(doSomething:) withObject:nil];

doSomething for BOOL parameter will have NO value

--

[self performSelector:@selector(doSomething:) withObject:[NSNumber numberWithBool:YES]];

doSomething for BOOL parameter will have YES value

--

[self performSelector:@selector(doSomething:) withObject:[NSNumber numberWithBool:NO]];

doSomething for BOOL parameter will have YES value (even passed is number with NO)

zvjerka24
  • 1,772
  • 1
  • 21
  • 27
4

It may not be the most elegant but I created another routine to call the routine with the bool. This keeps the selector stuff simple and doesn't need access to change the original routine.

[Added by Ash] Here's a sample that turns down the receiver's alpha value when a boolean property is set:

- (void) setVisible:(BOOL)visible
{
    _visible = visible;
    self.alpha = 0.0;
}

- (void) setVisibleUsingNSNumber:(NSNumber *)visible
{
    self.visible = [visible boolValue];
}

You can then trigger this after a delay thus:

[self performSelector:@selector(setVisibleUsingNSNumber:)
           withObject:[NSNumber numberWithBool:YES]
           afterDelay:0.5];

Note that it is also possible to use Grand Central Dispatch these days, in which case you can use the standard setter method inside a block bypassing all this extra code. However, it is much easier to cancel a performSelector call than it is to detect cancellation within a delayed block execution.

Ash
  • 9,064
  • 3
  • 48
  • 59
nimbusgb
  • 393
  • 1
  • 3
  • 12
  • Sounds interesting - could you clarify how exactly you achieved this? – Wirsing May 02 '13 at 09:51
  • I'm voting this up because it is the proper way to do things. Don't pretend you're overriding a method then deliberately pass it data that does not match its signature. – Ash Apr 10 '14 at 07:49
2

seen this trick form another post, but pass nil for NO and self for YES.

archieoi
  • 429
  • 1
  • 6
  • 9
  • 4
    DO NOT DO THIS. This MAY result (in very few cases, but still a few cases) in a NO despite you passed self! Reference: http://stackoverflow.com/a/7851666/874522 – Phil Jan 09 '13 at 22:00
2

With objective-C literals this can be expressed less verbose.

[self performSelector:@selector(doSomething:) withObject:@(YES) afterDelay:1.5];

Note the @(YES) i.s.o. YES

Pieter
  • 17,435
  • 8
  • 50
  • 89
  • 1
    Not sure why this got a down vote. Converting a boolean in a @() to a NSNumber is the quickest and neatest way to do this. – mylogon Dec 07 '15 at 10:00
2

You cannot send arguments to a selector like this.

You might want to have a look at following answer:

Creating a selector from a method name with parameters

Community
  • 1
  • 1
Faizan S.
  • 8,634
  • 8
  • 34
  • 63
1

If you are Passing the BOOL parameters in static then you can use the following..

------>To Pass 'NO' :


[self performSelector:@selector(doSomething:) withObject:0 afterDelay:1.5];

Now get the result after TimeInterval:

-(void)doSomething:(BOOL)isDoSomething
{
    NSLog(isDoSomething?@"Do Something":@"Don't Do Any Thing");
}

Here You will get ' Don't Do Any Thing ' in Log.


------>To Pass 'YES' :


[self performSelector:@selector(doSomething:) withObject:@"1" afterDelay:1.5];

Now get the result after TimeInterval:

-(void)doSomething:(BOOL)isDoSomething
{
    NSLog(isDoSomething?@"Do Something":@"Don't Do Any Thing");
}

Here You will get ' Do Something ' in Log.


Ashok
  • 5,585
  • 5
  • 52
  • 80
1

Instead of relying on NSInvocation or having to modify the method to use a NSNumber this can be done much more elegantly by creating a view e.g :

@interface CLLocationManager(CompatibleView)
@property (nonatomic) BOOL allowsBackgroundLocationUpdates;
@end

@implementation SampleClass
- (void)sampleFunc:(CLLocationManager *)locationManager {
    if ([locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
        locationManager.allowsBackgroundLocationUpdates = YES;
    }
}
@end
Pellet
  • 2,254
  • 1
  • 28
  • 20
0

I do it this way:

[_button performSelector:@selector(setUserInteractionEnabled:) withObject:[NSString stringWithFormat:@"YES"] afterDelay:nil];
iRehman
  • 1
  • 2
0

Try wrapping your bool in an NSNumber: [NSNumber numberWithBool:myBool]

And the you can get it back with [myNumber boolValue]

Sam
  • 1,504
  • 2
  • 13
  • 19
  • 3
    This is ok if you have access to the implementation. But useless if you're trying to use it for a method in a framework. – Jonathan. Aug 16 '11 at 08:52
  • Jonathan, see my answer below which gives a step-by-step guide (and code) for how to do exactly what you're looking for. Test and works fine with Cocoa Touch SDK (and would imagine any other frameworks). It works with primitives, enums and structs. – Jonathan Ellis Feb 10 '12 at 14:04