How to pass an argument to a method called in a NSTimer - objective-c

I have a timer calling a method but this method takes one paramether:
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:#selector(timer) userInfo:nil repeats:YES];
should be
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:#selector(timer:game) userInfo:nil repeats:YES];
now this syntax doesn't seems to be right. I tried with NSInvocation but I got some problems:
timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(timer:game)]];
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval
invocation:timerInvocation
repeats:YES];
How should I use Invocation?

Given this definition:
- (void)timerFired:(NSTimer *)timer
{
...
}
You then need to use #selector(timerFired:) (that's the method name without any spaces or argument names, but including the colons). The object you want to pass (game ?) is passed via the userInfo: part:
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval
target:self
selector:#selector(timerFired:)
userInfo:game
repeats:YES];
In your timer method, you can then access this object via the timer object's userInfo method:
- (void)timerFired:(NSTimer *)timer
{
Game *game = [timer userInfo];
...
}

As #DarkDust points out, NSTimer expects its target method to have a particular signature. If for some reason you can't conform to that, you can instead use an NSInvocation as you suggest, but in that case you need to fully initialise it with the selector, target and arguments. Eg:
timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(methodWithArg1:and2:)]];
// configure invocation
[timerInvocation setSelector:#selector(methodWithArg1:and2:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&arg1 atIndex:2]; // argument indexing is offset by 2 hidden args
[timerInvocation setArgument:&arg2 atIndex:3];
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval
invocation:timerInvocation
repeats:YES];
Calling invocationWithMethodSignature on its own doesn't do all that, it just creates an object that is able to be filled in in the right manner.

You can pass NSDictionary with named objects (like myParamName => myObject) through userInfo parameter like this
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval
target:self
selector:#selector(timer:)
userInfo:#{#"myParamName" : myObject}
repeats:YES];
Then in timer: method:
- (void)timer:(NSTimer *)timer {
id myObject = timer.userInfo[#"myParamName"];
...
}

Related

Why NSTimer sends positions every 5 seconds instead of every 60?

Why NSTimer sends positions every 5 seconds instead of every 60?
- (void)startTimer {
self.timer = [NSTimer scheduledTimerWithTimeInterval:60.0
target:self
selector:#selector(sendPosition)
userInfo:nil
repeats:YES];
}
- (void)stopTimer {
if(self.timer){
[self.timer invalidate];
self.timer = nil;
}
}
I suspect that there are multiple timers created due to multiple firing of startTimer function. To ensure that there is only one instance of such timer, you can implement the following.
- (void)startTimer {
// stop and remove timer first if it is already there
if(self.timer){
[self.timer invalidate];
self.timer = nil;
}
self.timer = [NSTimer scheduledTimerWithTimeInterval:60.0
target:self
selector:#selector(sendPosition)
userInfo:nil
repeats:YES];
}
This way, no matter how many times the startTimer was called,there is only one instance of it.

Method selector pointing at different object

The following method is in a viewController.
refreshTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(refreshLabels) userInfo:nil repeats:YES];
Is it possible to select a method inside appDelegate (or another class) at the selector instead of in the current object?
like:
AppDelegate *ad = (AppDelegate *) [[UIApplication sharedApplication] delegate];
refreshTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector([ad refreshLabels]) userInfo:nil repeats:YES];
Thanks!
try this.
AppDelegate *ad = (AppDelegate *) [[UIApplication sharedApplication] delegate];
refreshTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:ad selector:#selector(refreshLabels) userInfo:nil repeats:YES];
in your AppDelegate, there should be a declared refreshLabels method,
target
The object to which to send the message specified by aSelector when the timer fires. The target object is retained by the timer and released when the timer is invalidated.
aSelector
The message to send to target when the timer fires. The selector must correspond to a method that returns void and takes a single argument. The timer passes itself as the argument to this method.
the code in your question set 'self' as argument for target, but the method you are trying to call is in the AppDelegate

Using NSTimer, calling two functions with the same name, different parameters, both use scheduledTimerWithTimeInterval

I'm trying to write my own fade-ins and fade-outs using the AVAudioPlayer as a helper.
My problem is this: I have two method definitions with the same name, but one takes an int and the other takes no parameters. Is there a way for me to tell NSTimer which one to call? Couldn't really make sense of the documentation:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nstimer_Class/Reference/NSTimer.html
-(void) stopWithFadeOut
{
if (_player.volume > 0.1) {
[self adjustVolume:-.1];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(stopWithFadeOut) userInfo:NULL repeats:NO];
}
else {
[self stop];
}
}
and
-(void) stopWithFadeOut:(NSString *)speed
{
int incr = [speed intValue];
if (_player.volume > 0.1) {
[self adjustVolume:-incr];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(stopWithFadeOut) userInfo:NULL repeats:NO];
}
else {
[self stop];
}
}
Those actually have different names. The colon is significant, so the name (and hence the argument to #selector()) of the second method is stopWithFadeOut:.*
To create the timer, then, you want:
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(stopWithFadeOut:)
userInfo:NULL //^ Note! Colon!
repeats:NO];
However, this method is incorrect, because an NSTimer passes itself to its action method; it's not possible for you to pass in an arbitrary object. This is what the userInfo: parameter is for. You can sort of attach some object to the timer, and retrieve it inside the action method using the userInfo method, like so:
- (void)stopWithFadeOut: (NSTimer *)tim
{
NSString * speed = [tim userInfo];
int incr = [speed intValue];
if (_player.volume > 0.1) {
[self adjustVolume:-incr];
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(stopWithFadeOut:)
userInfo:speed
repeats:NO];
}
(Also note that this means your first method isn't really a correct NSTimer action method, because it doesn't have the NSTimer parameter.)
*The compiler wouldn't have let you declare or define two methods with the same name in one class, and the parameter type doesn't count as part of the selector, so trying to create -(void)dumblethwaite:(int)circumstance; and -(void)dumblethwaite:(NSString *)jinxopotamus; in one class doesn't work.
So this is what I came up with. Thanks Josh.
-(void) pauseWithFadeOut
{
NSNumber *incr = [[NSNumber alloc] initWithFloat:0.1];
while (_player.volume > 0.1) {
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(fadeOut) userInfo:incr repeats:NO];
}
}
-(void)fadeOut: (NSTimer*) deleg
{
int incr = (int) [deleg userInfo];
if (_player.volume > 0.1) {
[self adjustVolume:-incr];
}
}
-(void) pauseWithFadeOut:(NSString *)speed
{
NSNumber *incr = [[NSNumber alloc] initWithFloat:[speed floatValue]];
while (_player.volume > 0.1) {
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(fadeOut) userInfo:incr repeats:NO];
}
[self pause];
}
-(void) stopWithFadeOut
{
NSNumber *incr = [[NSNumber alloc] initWithFloat:0.1];
while (_player.volume > 0.1) {
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(fadeOut) userInfo:incr repeats:NO];
}
}
-(void) stopWithFadeOut:(NSString *)speed
{
NSNumber *incr = [[NSNumber alloc] initWithFloat:[speed floatValue]];
while (_player.volume > 0.1) {
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(fadeOut) userInfo:incr repeats:NO];
}
[self stop];
}

Pass method as argument

can I pass a method as an argument?
I don't succeed to pass the method targetOpenView in the example below:
-(void) targetTimeView:(id)sender {
[self TimeViewWithtimeInterval:.6 selector:targetOpenView]; //targetOpenView does NOT work
}
-(void) timeViewWithtimeInterval:(float)interval selector:openViewMethod{
[NSTimer scheduledTimerWithTimeInterval:interval target:self selector:#selector(openViewMethod) userInfo:nil repeats:NO];
}
Any suggestions how I could make this work? Thanks!
You need the #selector compiler directive to extract the select from a method name, like you did when creating the timer:
[self TimeViewWithtimeInterval:.6 selector:#selector(targetOpenView)];
And define your argument to the type SEL:
-(void) TimeViewWithtimeInterval:(float)interval selector:(SEL)openViewMethod
{
...
}
Then, when passing the argument to the NSTimer method you can leave off the #selector since the type is already a selector:
[NSTimer scheduledTimerWithTimeInterval:interval target:self
selector:#selector(openViewMethod) /* here */
userInfo:nil repeats:NO];
[NSTimer scheduledTimerWithTimeInterval:interval target:self
selector:openViewMethod /* pass it directly */
userInfo:nil repeats:NO];

Error: error: expected ')' before 'postData'

How to fix this error ?
Error: error: expected ')' before 'postData'
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self
selector: #selector(postData:#"xyz")
userInfo:nil
repeats: YES];
Functions called as selectors from timers cannot have parameters. If I remember correctly, you can use userInfo, which passes an array or dictionary to the selector.
do something like this:
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector: #selector(postData:)
userInfo:#"xyz"
repeats: YES];
- (void)postData:(NSTimer*)timer {
NSLog(#"userInfo = %#", timer.userInfo);
}
When we read the documentation of the method you are using, it seems it's not correctly called :
timer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(postData:)
userInfo:nil
repeats:YES];
And your postData must have the following signature :
- (void)postData:(NSTimer*)theTimer