How to pass arguments when calling a function with timer in object c
-(void)setX:(int)x andY:(int)y andObject:(Sprite*)obj
{
[obj setPosition:CGPointMake(x,y)];
}
Now I want to call the method above using the following timer.
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector() userInfo:nil repeats:NO];
What to install here?
How do I pass arguments? (according to my knowledge - the selector only specifies the calling method)
source to share
Copied from Matt Ball 's answer :
- (void)startMyTimer {
/* ... Some stuff ... */
NSDictionary *userDict;
userDict = [NSDictionary dictionaryWithObjectsAndKeys:someValue,
@"value1",
someOtherValue,
@"value2", nil];
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(callMyMethod:)
userInfo:userDict
repeats:YES];
}
- (void)callMyMethod:(NSTimer *)theTimer {
NSString *value1 = [[theTimer userInfo] objectForKey:@"value1"];
NSString *value2 = [[theTimer userInfo] objectForKey:@"value2"];
[self myMethod:value1 setValue2:value2];
}
source to share
Instead, you will need to use +[NSTimer scheduledTimerWithTimeInterval:invocation:repeats:]
. By default, the selector used to start the timer takes one parameter. If you want something different, you need to create an NSInvocation object that will use the timer.
source to share
If you have a rather complex set of arguments that you want to use to invoke a method, I would recommend capturing the arguments to something that contains a configuration and can do whatever it takes based on that configuration ...
Something with an interface like this:
PositionSetter.h:
@interface PositionSetter : NSObject
{
NSInteger x;
NSInteger y;
Sprite *target;
}
+ positionSetterWithX: (NSInteger) xPos y: (NSInteger) yPos sprite: (Sprite *) aSprite;
- (void) applyPosition;
@end
PositionSetter.m:
@interface PositionSetter()
@property(readwrite, nonatomic) NSInteger x;
@property(readwrite, nonatomic) NSInteger y;
@property(readwrite, nonatomic, retain) Sprite *target;
@end
@implementation PositionSetter
@synthesize x, y, target;
+ positionSetterWithX: (NSInteger) xPos y: (NSInteger) yPos sprite: (Sprite *) aSprite;
{
PositionSetter *positionSetter = [PositionSetter new];
positionSetter.x = xPos;
positionSetter.y = yPos;
positionSetter.target = aSprite;
return [positionSetter autorelease];
}
- (void) applyPosition;
{
[self.target setPosition:CGPointMake(self.x,self.y)];
}
@end
The usage is pretty simple:
positionSetter = [PositionSetter positionSetterWithX: 42 y: 21 sprite: mySprite];
[positionSetter performSelector: @selector(applyPosition) withObject: nil afterDelay: 1.0];
While a little more code, the resulting implementation will be fast enough - probably faster than NSInvocation, but fast enough to be irrelevant given that it would cause drawing - and a hell of a lot more flexible. I could easily see the refactoring above in driving a car, say CoreAnimation.
source to share
If you are using a target action timer, you cannot immediately call the timer of an arbitrary method. A timer action must have a very specific signature. You can pass additional data in the userinfo dictionary and activate the timer action with the method you ultimately want, or you can use the invocation form as Dave said. Personally, I usually do the former because I find NSInvocations annoying and set one, it can actually take more code than just writing an intermediate method.
source to share
As an alternative to NSTimer, on iOS 4.0+ and 10.6+, you can use the Grand Central Dispatch and dispatch sources for this using a block. Apple has the following code for this in the Concurrency Programming Guide :
dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
Then you can set up a one second timer event using the following code:
dispatch_source_t newTimer = CreateDispatchTimer(1ull * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10, dispatch_get_main_queue(), ^{
[self setX:someValue andY:otherValue andObject:obj];
});
as long as you save and release your timer when it's done. It might even allow you to start a timer to execute items on a background thread using a parallel queue instead of the main queue used above.
This can eliminate the need for boxing and unboxing arguments.
source to share