35

Is there any way that I can pass arguments in selector?

example: I have this method

- (void)myMethod:(NSString*)value1 setValue2:(NSString*)value2{

}

and I need to call this function through a selector passing two arguments.

[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(/*my method*/) userInfo:nil repeats:YES];

How can I do this?

Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
isiaatz
  • 1,125
  • 4
  • 15
  • 22

5 Answers5

56

You could use the NSTimer method:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
                                 invocation:(NSInvocation *)invocation
                                    repeats:(BOOL)repeats;

Instead, since an NSInvocation object will allow you to pass arguments; an NSInvocation object is, as the docs define it:

an Objective-C message rendered static, that is, it is an action turned into an object.

Whilst creating an NSTimer object using a selector requires the format of the method being:

- (void)timerFireMethod:(NSTimer*)theTimer

An NSInvocation allows you to set the target, the selector, and the arguments that you pass in:

SEL selector = @selector(myMethod:setValue2:);

NSMethodSignature *signature = [MyObject instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];

NSString *str1 = @"someString";
NSString *str2 = @"someOtherString";

//The invocation object must retain its arguments
[str1 retain];
[str2 retain];

//Set the arguments
[invocation setTarget:targetInstance];
[invocation setArgument:&str1 atIndex:2];
[invocation setArgument:&str2 atIndex:3];

[NSTimer scheduledTimerWithTimeInterval:0.1 invocation:invocation repeats:YES];

Where MyObject is the class that myMethod:setValue2: is declared and implemented on – instanceMethodSignatureForSelector: is a convenience function declared on NSObject which returns an NSMethodSignature object for you, to be passed to NSInvocation.

Also, to note, with setArgument:atIndex:, the indices for arguments to be passed to the method set as the selector start at index 2. From the docs:

Indices 0 and 1 indicate the hidden arguments self and _cmd, respectively; you should set these values directly with the setTarget: and setSelector: methods. Use indices 2 and greater for the arguments normally passed in a message.

Alex Rozanski
  • 37,815
  • 10
  • 68
  • 69
  • 1
    This is a better answer than mine. You should do this to avoid polluting your implementations with unnecessary methods. – Matt Ball Aug 28 '09 at 23:05
  • 3
    Note that, were `str1` and `str2` not statically allocated, you'd be leaking them. `NSTimer` is documented as sending `-retainArguments` to its invocation object, but it would not hurt for you to send `-retainArguments` to the invocation object yourself. The problem is retaining the arguments yourself, then telling the invocation to retain them, as well. Don't retain invocation arguments yourself! Let the invocation handle it – and, here, let the timer handle it itself. – Jeremy W. Sherman Sep 03 '09 at 19:29
  • 3
    Don't hardcode the class of `targetInstance`, as was done in retrieving the method signature. If you really wanted to use `+instanceMethodSignatureForSelector:`, you could use `[[targetInstance class] instanceMethodSignatureForSelector:selector]`, but that's needlessly complex – just ask the object itself for the method signature using `[targetInstance methodSignatureForSelector:selector]`. – Jeremy W. Sherman Sep 03 '09 at 19:31
27

For scheduledTimerWithTimeInterval:, the selector you pass can only have one argument. Further, its one argument must be an NSTimer * object. In other words, the selector must take the following form:

- (void)timerFireMethod:(NSTimer*)theTimer

What you could do is store the arguments in the userInfo dictionary and call the selector you want from the timer callback:

- (void)startMyTimer {
    /* ... Some stuff ... */
    [NSTimer scheduledTimerWithTimeInterval:0.1 
                                     target:self 
                                   selector:@selector(callMyMethod:) 
                                   userInfo:[NSDictionary dictionaryWithObjectsAndKeys:someValue, 
                       @"value1", someOtherValue, @"value2", nil] 
                                    repeats:YES];
}

- (void)callMyMethod:(NSTimer *)theTimer {
    NSString *value1 = [[theTimer userInfo] objectForKey:@"value1"];
    NSString *value2 = [[theTimer userInfo] objectForKey:@"value2"];
    [self myMethod:value1 setValue2:value2];
}
David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
Matt Ball
  • 6,608
  • 1
  • 32
  • 31
  • 2
    This is correct for the specific method you have posted above isiaatz. If that was just an example and you want to know in general how to send multiple arguments to a selector please amend your question – h4xxr Aug 28 '09 at 22:55
  • I like this better than the accepted answer since it's more concise. – Karsten Silz Apr 02 '10 at 14:19
2

Looks like a job for blocks (assuming this is targeted for Snow Leopard.)

-jcr

NSResponder
  • 16,861
  • 7
  • 32
  • 46
0

blocks seem like an obvious answer now... but there is another idiom that was very commonplace in the classic runtime, pickle your arguments in a single object:

 - (void)doMyMethod:(NSDictionary *)userInfo
 {
     [self myMethod: [userInfo objectForKey:@"value1"] setValue2: [userInfo objectForKey:@"value2"]];
 }
 - (void)myMethod:(NSString*)value1 setValue2:(NSString*)value2{

}

now you can dispatch to

[self performSelector:@selector(doMyMethod:) withObject:@{@"value1":@"value1",@"value2":@"value2"}];
Grady Player
  • 14,399
  • 2
  • 48
  • 76
-2
@selector(myMethod:setValue2:)

Since the selector for your method isn't just called myMethod but instead myMethod:setValue2:.

Also (and I could be off base here), I believe technically you can drop the words between colons and thus also use @selector(myMethod::) but don't quote me on this unless others can confirm it.

jbrennan
  • 11,943
  • 14
  • 73
  • 115
  • 2
    That is incorrect. The selector is the entire name of the method, including everything between the colons. `myMethod::` and `myMethod:setValue2:` are distinct selectors, which would be associated with distinct implementations unless you've worked some runtime magic. – Jeremy W. Sherman Sep 03 '09 at 19:24