views:

975

answers:

4

Let's say I have a method with this signature:

 -(void)plotPoly:(Polygon *)poly WithColor:(UIColor *)color AndFill:(BOOL)filled;

How do I get that UIColor and BOOL in there as well as the Polygon?

Should I wrap them in a NSArray and pull them out inside the called method? That would mean I have to change the method sig, right?

Is there a more elegant way to do it?

A: 

I believe NSArray is a reasonable solution, and yes that would mean changing the method signature to taking an NSArray* as its sole argument.

nall
+2  A: 

Joe Hewitt's Three20 library has some advanced versions of performSelector that you might find useful (I only post a snippet):

- (id)performSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 {
  NSMethodSignature *sig = [self methodSignatureForSelector:selector];
  if (sig) {
    NSInvocation* invo = [NSInvocation invocationWithMethodSignature:sig];
    [invo setTarget:self];
    [invo setSelector:selector];
    [invo setArgument:&p1 atIndex:2];
    [invo setArgument:&p2 atIndex:3];
    [invo setArgument:&p3 atIndex:4];
    [invo invoke];
    if (sig.methodReturnLength) {
      id anObject;
      [invo getReturnValue:&anObject];
      return anObject;
    } else {
      return nil;
    }
  } else {
    return nil;
  }
}

Just add them to a Category of NSObject.

MrMage
How would I implement the afterDelay: function?
willc2
+2  A: 

Still not exactly what I would call elegant, but less yuck than having to change the whole API is NSInvocation:

Polygon *poly;
UIColor *color;
BOOL filled;
// Assume the above variables exist
NSInvocation *inv = [NSInvocation invocationWithMessageSignature:[target messageSignatureForSelector:message]];
[inv setArgument:&poly atIndex:2];
[inv setArgument:&color atIndex:3];
[inv setArgument:&filled atIndex:4];
[inv performSelector:@selector(invokeWithTarget:) withObject:target afterDelay:1];

The other best option is just to create a wrapper method that calls the original method you want with appropriate arguments (perhaps given as a dictionary or array), which matches the signature needed to perform after a delay.

Chuck
Why does the argument index start at 3 and not 2 (as the docs seem to indicate)?
willc2
It should start at 2, not 3.
Brad Larson
It starts at 2. Arguments 0 and 1 are reserved for the hidden `self` and `_cmd` parameters.
Dave DeLong
Sorry about that. Typoed the first and then just incremented for the next ones.
Chuck
do you mean 'invocationWithMethodSignature' ? Still this method does not work for me.
Dimitris
What happens to the lifetime of this NSInvocation object? If you perform the selector after a delay, at the end of the current runloop this NSInvocation object should autorelease, and hence won't be around for the delay. And if calling this from an object that itself won't be around long (i.e. a modal view controller calling a delegate on the next run loop) then you can't store the NSInvocation in the calling object.
Michael Waterfall
+5  A: 

I answered a fairly similar question a few weeks ago. Answer below edited for this question.

In general, I avoid NSInvocation for this kind of work. It tends to be a maintenance headache and, in particular, creates difficulty in refactoring in the future.

First, given this method:

 -(void)plotPoly:(Polygon *)poly WithColor:(UIColor *)color AndFill:(BOOL)filled;

It would generally be declared as:

 -(void)plotPoly:(Polygon *)aPoly color:(UIColor *)aColor filled:(BOOL)filledFlag;

This follows the naming conventions a bit more closely.

Now, what I would do is actually capture the arguments into a simple class that provides an -invoke method.

Something with an interface like this:

PolyPlotter.h:

@interface  PolyPlotter : NSObject
{
    Polygon *poly;
    UIColor *color;
    BOOL filled;
}

+ plotterWithPoly: (Polygon *) aPoly color: (UIColor *) aColor filled: (BOOL) filledFlag; 

- (void) plot;
@end

PolyPlotter.m:

@interface PolyPlotter()
@property Polygon *poly;
@property UIColor *color;
@property BOOL filled;
@end

@implementation PolyPlotter
@synthesize poly, color, filled;

+ plotterWithPoly: (Polygon *) aPoly color: (UIColor *) aColor filled: (BOOL) filledFlag; 
{
    PolyPlotter *polygonPlotter = [PolyPlotter new];
    polygonPlotter.poly = aPoly;
    polygonPlotter.color = aColor;
    polygonPlotter.filled = filledFlag;
    return [polygonPlotter autorelease];
}

- (void) plot;
{
    // ... do your plotting here ...
}
@end

Usage is straightforward. Just create an instance of PolygonPlotter and tell it to perform the selector plot after delay or on main thread or whatever.

Given the question, I suspect that you might need a bit more context at the time of drawing? If so, you could pass that information as an argument to -plot by, say, declaring the method as:

- (void) plot: (UIView *) aViewToPlotIn;

Or something like that.

Like I said, slightly more code, but much more flexible and refactorable than the NSInvocation pattern. For example, you could quite easily make the PolygonPlotter something that could be archived.

bbum