Posted by: Brian Knorr on: March 26, 2009
For UISpec I needed a an easy way to create and cache a method call on an object. I know that NSInvocation is the class to go with, but it’s very cumbersome to create and use. Luckily I found the blog post Callable Objects in Cocoa, which pointed me in the right direction, so I took what they suggested one step further and made it dynamic using message forwarding.
Say you have an address book object that models a list of addresses and you want to “record” a call that does a find by first and last name and “play” the call later.
//Create a Recordable instance with the addressBook as its target Recordable *dvd = [Recordable withTarget:addressBook]; //Record the call you want to make [dvd findAddressByFirstName:@"Joe" lastName:@"Smith"]; //Play it to get the results of the call Address *address = dvd.play;
Pretty nifty huh? Here is the source code. Enjoy!
Recordable.h
@interface Recordable : NSObject {
id target, play;
NSInvocation *invocation;
}
@property(nonatomic, retain) id target;
@property(nonatomic, retain) NSInvocation *invocation;
@property(nonatomic, readonly) id play;
+(id)withTarget:(id)target;
@end
Recordable.m
<ol>
<li>import "Recordable.h"</li>
</ol>
@implementation Recordable
@synthesize target, invocation;
+(id)withTarget:(id)target {
return [[[self alloc] initWithTarget:target] autorelease];
}
-(id)initWithTarget:(id)_target {
if (self = [super init]) {
self.target = _target;
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [target methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation setTarget:target];
[anInvocation retainArguments];
[anInvocation setReturnValue:&self];
self.invocation = anInvocation;
}
-(id)play {
id value;
[invocation invoke];
NSString *returnType = [NSString stringWithFormat:@"%s", [[invocation methodSignature] methodReturnType]];
if (![returnType isEqualToString:@"v"]) {
id value;
[invocation getReturnValue:&value];
return value;
} else {
return nil;
}
}
-(void)dealloc {
self.target = nil;
self.invocation = nil;
[super dealloc];
}
@end