Blog

Easy Delayed Messaging using NSProxy and NSInvocation

Sometimes it’s necessary to perform an action some time in the future, whether it’s disabling a button for a certain time interval after it’s pressed, performing an animation after a short wait, or triggering a reload of some data.

NSTimer is great for that purpose, as well as repeatedly performing actions, but it’s most convenient utility method, scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: only takes one ‘id‘ argument. Using the NSInvocation equivalent, scheduledTimerWithTimeInterval:invocation:repeats: requires creating and setting up the NSInvocation itself, which is always verbose and a pain.

NSProxy is a wonderful little construct that lets us interact with it like we were talking to the original object. I first learned how it works from a post by Shaun Wexler which shows an easier way to send a message on the main thread, by using the NSInvocation given via the NSProxy’s forwardInvocation: method. The same technique can be used to easily create a configured NSTimer.

So, instead of some awful thing like this:

NSInvocation *i = [NSInvocation invocationWithMethodSignature:[mapView methodSignatureForSelector:@selector(selectAnnotation:animated:)]];
[i setTarget:mapView];
MKAnnotation *annotation = view.annotation;
[i setArgument:&annotation atIndex:3];
BOOL flag=YES;
[i setArgument:&flag atIndex:4];
[NSTimer scheduledTimerWithTimeInterval:0.5 invocation:i repeats:NO];

We can do this:

[(MKMapView*)[TPTimerProxy scheduledTimerProxyWithTarget:mapView timeInterval:0.5 repeats:NO] selectAnnotation:view.annotation animated:YES];

Here’s the juice:

@interface TPTimerProxy : NSProxy {
    id target;
    NSTimeInterval timeInterval;
    BOOL repeats;
 
    NSTimer *timer;
}
+ (TPTimerProxy*)scheduledTimerProxyWithTarget:(id)target timeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats;
- (void)invalidate;
@end
 
 
@implementation TPTimerProxy
 
- (id)initWithTarget:(id)aTarget timeInterval:(NSTimeInterval)aTimeInterval repeats:(BOOL)repeatFlag {
    target = aTarget;
    timeInterval = aTimeInterval;
    repeats = repeatFlag;
    return self;
}
 
+ (TPTimerProxy*)scheduledTimerProxyWithTarget:(id)target timeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats {
    return [[[TPTimerProxy alloc] initWithTarget:target timeInterval:timeInterval repeats:repeats] autorelease];
}
 
- (void)invalidate {
    if ( timer ) {
        [timer invalidate];
        timer = nil;
    }
}
 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [target methodSignatureForSelector:selector];
}
 
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation setTarget:target];
    timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval invocation:invocation repeats:repeats];
}
 
@end
, , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

One Comment

  1. Chris Rivers
    Posted June 13, 2013 at 11:21 pm | Permalink

    Would it be possible to release this code as a package and include a license (even if it’s “public domain license”)? It seems like a useful snippet, but at work we’re can’t use any unlicensed code. Thanks!