Silver Challenge Solution - NSInvocation


#1

For my Cell Base Class i wanted a very flexible way to call methods on the controller. This included being able to call a method with an arbitrary name of parameters (not just 1 or 2). According to the Documentation this is done with an NSInvocation.

This is my base class

ActionForwarderUITableViewCell.h

@interface ActionForwarderUITableViewCell : UITableViewCell

@property (weak, nonatomic) id controller;
@property (weak,nonatomic) UITableView *tableView;
-(void)callControllerSelectorNamed:(NSString *)selectorName withParams:(NSArray *)params;
@end

ActionForwarderUITableViewCell.m

@implementation ActionForwarderUITableViewCell

@synthesize controller, tableView;

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Initialization code
    }
    return self;
}

-(void)callControllerSelectorNamed:(NSString *)selectorName withParams:(NSArray *)params
{
    // if no selector is called then ignore...
    if([self controller] == nil)
    {
        NSLog(@"callControllerSelectorNamed  - Error : No controller set!");
        return;
    }

    SEL selector = NSSelectorFromString(selectorName);

    NSMethodSignature *aSignature;
    NSInvocation *anInvocation;
    
    aSignature = [[[self controller] class] instanceMethodSignatureForSelector];
    if(aSignature == nil)
    {
        NSLog(@"callControllerSelectorNamed  - Error : signature not found!");
        return;
    }
    anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
    [anInvocation setSelector];
    [anInvocation setTarget:[self controller]];
    
    for (int i = 0; i < [params count]; i++) {
        id param = [params objectAtIndex:i];
        if(param != nil)
            [anInvocation setArgument:&param atIndex: i + 2];
    }
    [anInvocation invoke];
}

@end

With this is very easy to call any parameter on the controller and then pack any number of parameters in the params NSArray. Like so:

StepperUITableViewCell.h

[code]#import “ActionForwarderUITableViewCell.h”

@interface StepperUITableViewCell : ActionForwarderUITableViewCell

@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UIStepper *stepper;
@property (weak, nonatomic) IBOutlet UILabel *valueLabel;

  • (IBAction)onStepperValueChanged:(id)sender;
    -(void)setValueInDollars:(int)value;

@end[/code]

StepperUITableViewCell.m

[code]@implementation StepperUITableViewCell
@synthesize nameLabel, valueLabel, stepper;

  • (IBAction)onStepperValueChanged:(id)sender {

    NSString *selector = @“setValueInDollars:atIndexPath:”;
    NSIndexPath *indexPath = [[self tableView] indexPathForCell:self];
    NSNumber *v = [NSNumber numberWithDouble:[stepper value]];
    NSArray *params = [NSArray arrayWithObjects: v, indexPath ,nil];

    [self callControllerSelectorNamed:selector withParams:params];
    }
    -(void)setValueInDollars:(int)value
    {
    if(value > 50)
    {
    [[self valueLabel] setTextColor:[UIColor greenColor]];
    }
    else
    {
    [[self valueLabel] setTextColor:[UIColor redColor]];
    }
    [[self valueLabel] setText:[NSString stringWithFormat:@"$%i",value]];
    [stepper setMaximumValue:0];
    [stepper setMaximumValue:1000];
    [stepper setValue:value];
    }[/code]

And then on ItemsViewController.m

We can just do this:

...

-(void)setValueInDollars:(NSNumber *)valueInDollars atIndexPath:(NSIndexPath *)ip
{
     BNRItem *i = [[[BNRItemstore sharedStore] allItems] objectAtIndex:[ip row]];
    [i setValueInDollars:[valueInDollars integerValue]];
    [[self tableView] reloadData];
}

...

The only problem is that you have to know the method name and expected params in the controller beforehand, but i don’t see how the other approach with performSelector is any better at that.


#2

I’ve created a solution that seems to be working. I’ll post the code if you want. Basically, what I did was to create the selector string in the subclassed cell along with an NSArray. This array holds the variables/objects needed in the method in the final method call. I really didn’t like the NSInvocation idea so went with this way instead.

As in your method, the method name must be known ahead of time, but we have to start with some information. I don’t think that is a bad way to go.

Jeff