Nifty Box News

Tuesday, December 12, 2006

NSInvocation cleans code

Update: (2008-08-07)
There's been a small (but potent) bug in the given implementation. I somehow move the release of the invocation:
[myInvocation release]; myInvocation = nil;
into the dealloc method of the _UFLatePerformer class. This is bollocks, because the invocation contains _UFLatePerformer as target itself. Therefore I have to release the invocation as soon as it has been invoked. Otherwise there is a retain-cycle and the dealloc-method of _UFLatePerformer is never called.
The code below has been updated accordingly.


During the development of Nifty Box I've grown to use NSInvocations more and more. One thing I am using this class especially for is a wrapper for NSObject's performSelector:withObject:afterDelay:. This method invokes the selector with one argument after a specified delay. My problem was that I can only pass one argument for the selector, but it often happens that I want to invoke a method with more than one argument. So I had to pack all the arguments into a NSDictionary, write a proxy method, inside this proxy function unpack the arguments from the dictionary and invoke the original method. This is messy.

For this I have written a small category add-on for NSObject:

@interface _UFLatePerformer : NSObject {
NSInvocation *myInvocation;
id target;
}
-(id)initWithTarget:(id)theTarget;
-(void)performLate;
@end

@interface NSObject (NSObject_laterInvocation)
-(id)performAfterDelay:(NSTimeInterval)delay;
@end

@implementation _UFLatePerformer

- (id) initWithTarget:(id)theTarget {
[super init];
if (self != nil) {
target = theTarget;
[target retain];
}
return self;
}

- (void) dealloc {
[target release]; target = nil;

[super dealloc];
}


-(void)forwardInvocation:(NSInvocation *)invocation;
{
myInvocation = invocation;
[myInvocation retain];
[myInvocation retainArguments];
}

-(BOOL)respondsToSelector:(SEL)aSelector;
{
BOOL result = [super respondsToSelector:aSelector];
if (result == NO)
result = [target respondsToSelector:aSelector];
return result;
}

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;
{
NSMethodSignature *result = [super methodSignatureForSelector:aSelector];
if (!result)
result = [target methodSignatureForSelector:aSelector];
return result;
}


-(void)performLate;
{
[myInvocation invokeWithTarget:target];
[myInvocation release]; myInvocation = nil;
}

@end


@implementation NSObject (NSObject_laterInvocation)

-(id)performAfterDelay:(NSTimeInterval)delay;
{
_UFLatePerformer *latePerformer = [[_UFLatePerformer alloc]
initWithTarget:self];
[latePerformer performSelector:@selector(performLate)
withObject:nil afterDelay:delay];
[latePerformer release];
return latePerformer;
}

@end


This basically works like the NSUndoManager's [[undoManager prepareWithInvocationTarget:self] myMethod:someArgument anotherArgument:secondArgument]

You use this new method like that:

[[myObject performAfterDelay:0.0] someMehodWithArgument1:argument1 argument2:argument2]


This is an implementation of the Trampoline Object.

Feel free to use this snippet as you wish. No warranty however.

Labels: , ,

3 Comments:

  • I &heart; trampolines.

    By Anonymous Anonymous, At 12/20/2006 6:28 PM  

  • This has really helped me out a lot -- but the one thing I've been struggling with is being able to do the equivalent of:

    [NSObject cancelPreviousPerformRequestsWithTarget:self]

    Is there any way to do this, so my view's deallocation can kill all remaining delayed performance requests?

    By Blogger jeffreality, At 2/07/2010 5:17 AM  

  • The cancelPreviousPerformRequestsWithTarget message has to be send to the temporary _UFLatePerformer object. Therefore you have to have access to it.

    So, taking this into account, I would modify the usage snippet to;


    id lateP = [myObject performAfterDelay:0.0];
    [lateP someMehodWithArgument1:argument1 argument2:argument2];

    now you can cancel this request with:

    [NSObject cancelPreviousPerformRequestsWithTarget:lateP];

    I think this should work, but I have not tested it.

    By Blogger Tim Scheffler, At 2/09/2010 3:56 PM  

Post a Comment



<< Home