tags:

views:

155

answers:

2

I'd like (at runtime) to bind a parameter to a function as you can do in boost::bind - a little like the following:

-(void)myFuncWithParameter:(NSString*)param {
   NSLog(param);
}

-(void)init {

   UIButton *helloButton = [UIButton buttonWithType:UIButtonTypeCustom];
   [helloButton addTarget:self action:@selector(myFuncWithParameter:@"hello") forControlEvents:UIControlEventTouchUpInside];

}

So... I'm dynamically binding (at runtime) the value @"hello" to a parameter.

Obviously the above isn't the correct Syntax. Does anyone know if this is possible and the correct syntax?

Cheers, Nick.

+3  A: 

The short answer is no, or at least not at that level.

The long answer is that it is technically possible to build something akin to using NSInvocations (and/or forwardInvocation:), doing something clever in methodForSelector: and or by dynamically registering method implementations, but it is very tricky, especially if you care at all about speed.

If I had some code where building curried methods like that was really worthwhile, what I would do is something like this (written in this comment, untested);

//FIXME: In a real implementation you would do some mangling, this code will get confused if you have _s in the curried selector, and thus could be exploitable

//This method makes a unique selector by mangling the arguments
- (SEL) selectorForSelector:(SEL)bindSel withString:(NSString *)bindString {
  NSString *mangle = [NSString *stringWithFormat:@"LGBind_%@_%@"], NSStringFromSelector(bindSel), bindString];
  SEL retval = NSSelectorFromString(mangle);

  //Register the imp. You probably want to check if it is already reg
  if (![self respondsToSelector:retval]) {
    class_addMethod([self class], retval, LGBind_IMP, "v@:")l
  }
}

//Generic dispatcher imp
void LGBind_IMP(id self, SEL _cmd) {
  NSString *selectorName = NSStringFromSelector(_cmd);
  NSArray *array [selectorName componentsSeparatedByString:@"_"];

  //Skip index 0; it is @"LGBind"
  NSString *originalSelectorString = [array objectAtIndex:1];
  NSString *originalArgString = [array objectAtIndex:2];

  //Get our the SEL and the IMP
  SEL originalSEL = NSSelectorFromString(originalSelectorString);
  IMP originalIMP = [self methodForSelector:originalSEL];

  //call the original imp
  originalIMP([self class], originalSEL, originalArgString);
}

Obviously depending on your exact needs you could do things somewhere differently, for instance you could lazily by the imps in forwardInvocation, or stash data about the managled selector in a dict in the instance instead of just managling it into the selector name.

Louis Gerbarg
+2  A: 

The general answer is that the target-action mechanism only allows for a target, a sender and a message that takes the sender; therefore, if you need to access data, you must get it from the target or the sender.

One option would be to create a class that represents the binding of a parameter value, a method and an object. This class would have an action that invokes the method on the object, passing the value. Use an instance of this class as the target. Here's a simplistic example:

@interface UnaryBinder : NSObject {
    id target;
    SEL selector;
    id parameter;
}
@property id target;
@property SEL selector;
@property (retain) id parameter;
-(id)initWithTarget:(id)anObject selector:(SEL)aSelector param:(id)aParameter;
-(void)action:(id)sender;
@end

@implementation UnaryBinder
...
-(void)action:(id)sender {
    [target performSelector:selector withObject:parameter];
}
@end

If you want to support an arbitrary number of parameters, you'd need to use NSInvocation (as Louis mentions) rather than performSelector:withObject. Of course, controls don't retain their targets, so you need some way of keeping the UnaryBinder around. At that point, you might as well skip the special class and just store the data in the control, as you mention in your comment about using KVP. Alternatively, factor out the action into a controller class and use an instance of that as the target. UnaryBinder and its ilk doesn't really offer any advantages when it comes to target-action. For related topics, google "higher order messaging".

outis
Nick Cartwright