How to get performSelector to work with NSInvocation?

2.2k views Asked by At

I need to pass the touches and event from touchesBegan to my own method called by a performSelector. I am using an NSInvocation to package up the arguments but I'm having problems with the target.

The reason that I do it this way is so I can handle other scroll events.

Here is my code:

- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{           
    UITouch *theTouch = [touches anyObject];

    switch ([theTouch tapCount]) 
    {
        case 1:
            NSInvocation *inv = [NSInvocation invocationWithMethodSignature: [self methodSignatureForSelector:@selector(handleTap: withEvent:)]];
            [inv setArgument:&touches atIndex:2];
            [inv setArgument:&event atIndex:3];
            [inv performSelector:@selector(invokeWithTarget:) withObject:[self target] afterDelay:.5];
            break;
    }
}

Where handleTap is defined as:

-(IBAction)handleTap:(NSSet *)touches withEvent:(UIEvent *)event 
{
    [super touchesBegan:touches withEvent:event];
}

My problem is that when I compile it I get a warning:

'CategoryButton' many not respond to '-target'

and when I run it, it crashes with a:

-[CategoryButton target]: unrecognized selector sent to instance 0x5b39280

I must admit I don't really understand what the target is trying to do here and how it is set.

Thanks for your help.

2

There are 2 answers

2
Craig Otis On BEST ANSWER

I think you need to take some time to read through your code carefully, line-by-line.

[inv performSelector:@selector(invokeWithTarget:) withObject:[self target] afterDelay:.5];

This isn't doing what you think it is. One half of one second after this method is executed, this will happen:

[inv invokeWithTarget:[self target]];

First, your class CategoryButton doesn't have a method called target. Second, why the delay? If you're using these touches for scrolling, a delay of 0.5 seconds is going to be extremely painful for users.

Why are you using the NSInvocation class at all? If you really need the delay, you can simply use the performSelector: method on your CategoryButton instance:

NSArray *params = [NSArray arrayWithObjects:touches, event, nil];
[self performSelector:@selector(handleTap:) withObject:params afterDelay:0.5];

Notice the performSelector: methods only support one argument, so you have to wrap them in an NSArray. (Alternatively, you can use an NSDictionary.)

You will have to update your handleTap: method to accept an NSArray/NSDictionary and snag the arguments as needed.

But again, if you don't need the delay, why not just call the method yourself:

- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{           
    UITouch *theTouch = [touches anyObject];

    switch ([theTouch tapCount]) 
    {
        case 1:
            [super touchesBegan:touches withEvent:event];
        break;
    }
}

Maybe I'm misunderstanding your intentions, but it seems you're making this way more complicated than it needs to be.

1
lxt On

I must admit I don't really understand what the target is trying to do here and how it is set.

The target is the object which you want the invocation to be performed on. You're getting a crash because your chosen object - [self] - doesn't respond to the target message. I think you might just have got a bit confused over what you need to pass in.

With your current code you're asking your invocation to be performed on the target property of self. You probably don't want to do this - I would assume you want your invocation to be performed simply on self. In which case, use this:

[inv performSelector:@selector(invokeWithTarget:) withObject:self afterDelay:.5];