Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use performSelector:withObject:afterDelay: on a method with multiple arguments

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?

like image 246
willc2 Avatar asked Oct 18 '09 09:10

willc2


3 Answers

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.

like image 160
Chuck Avatar answered Nov 17 '22 02:11

Chuck


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.

like image 36
bbum Avatar answered Nov 17 '22 01:11

bbum


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.

like image 4
MrMage Avatar answered Nov 17 '22 01:11

MrMage