Objective-C: How to call performSelector with a BOOL typed parameter?
Asked Answered
E

14

47

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 ?

Expiry answered 16/8, 2011 at 8:42 Comment(0)
D
56

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];
Dispute answered 16/8, 2011 at 8:51 Comment(5)
This is fine if you are able to manipulate the signature of the target selector. If not you should use NSInvocation.Wrasse
How bout an answer or a link to an answer suggesting an NSInvocation solution?Campball
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.Arlina
NSInvocation is betterSlingshot
@Wrasse 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.Ardeb
W
73

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];
Wrasse answered 4/1, 2013 at 17:25 Comment(6)
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.Psychophysics
@Psychophysics - 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?Wrasse
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.Psychophysics
@Psychophysics - I believe the call to invoke is synchronous.Wrasse
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...Psychophysics
@Psychophysics - 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.Wrasse
D
56

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];
Dispute answered 16/8, 2011 at 8:51 Comment(5)
This is fine if you are able to manipulate the signature of the target selector. If not you should use NSInvocation.Wrasse
How bout an answer or a link to an answer suggesting an NSInvocation solution?Campball
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.Arlina
NSInvocation is betterSlingshot
@Wrasse 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.Ardeb
A
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.

Atwood answered 24/1, 2014 at 20:19 Comment(3)
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.Adige
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.Ardeb
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.Ardeb
P
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];
});
Protuberate answered 7/3, 2013 at 6:7 Comment(1)
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 TimerLonlona
T
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

Toxoplasmosis answered 29/6, 2012 at 13:8 Comment(1)
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. -rrhSuffragan
A
5

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.

Arron answered 26/2, 2013 at 17:50 Comment(2)
Sounds interesting - could you clarify how exactly you achieved this?Perchance
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.Incogitant
C
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)

Cryohydrate answered 30/1, 2015 at 9:40 Comment(1)
This works perfectly - a much neater solution. For readability I use as @pieter suggested @(YES) for YES and nil for NOChristine
M
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

Moten answered 16/8, 2011 at 8:47 Comment(0)
D
2

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

Dickey answered 29/6, 2012 at 0:28 Comment(1)
DO NOT DO THIS. This MAY result (in very few cases, but still a few cases) in a NO despite you passed self! Reference: https://mcmap.net/q/217494/-how-to-use-performselector-withobject-afterdelay-with-primitive-types-in-cocoaHexamerous
U
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

Upward answered 22/5, 2014 at 13:45 Comment(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.Tiller
H
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.


Halfhardy answered 11/12, 2013 at 8:8 Comment(0)
E
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
Elmore answered 2/9, 2015 at 0:54 Comment(0)
P
0

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

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

Pulido answered 16/8, 2011 at 8:45 Comment(2)
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.Budweis
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.Stagy
S
0

I do it this way:

[_button performSelector:@selector(setUserInteractionEnabled:) withObject:[NSString stringWithFormat:@"YES"] afterDelay:nil];
Shem answered 28/5, 2013 at 9:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.