Is there a way to pass the entire argument list to another method in Objective C?
I'd like to be able to pass all the arguments received in my method to a different method, as generically as possible.
Ideally, this would be done by passing a dictionary or some system variable (similar to _cmd).
In other words, I'm looking for something like the arguments
array in ja开发者_如何学Cvascript, or anything giving me access to the currently called method's list of arguments.
I think what you are looking for is NSObject's forwardInvocation:
It gets passed an NSInvocation
object that contains the information you want. NSInvocation
also has a nice method called invokeWithTarget:
that pretty much forwards the method call just like if you've called it directly.
The runtime will call fowardInvocation:
if you're object is sent a message that it doesn't have a method for, provided you also override methodSignatureForSelector:
so the runtime can create the NSInvocation object.
If all your arguments are objects the method forwardInvocation
method will look something like this:
@implementation Forwarder
@synthesize friendObject;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [self.friendObject methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog("Forwarding method: %@", [anInvocation selector]);
NSMethodSignature *sig = [anInvocation methodSignature];
// Get the juicy argument list info from [anInvocation methodSignature]
// NOTE: Arguments 0 and 1 are for self and _cmd So we'll skip those.
int numberOfArgs = [[anInvocation methodSignature] numberOfArguments];
// Assuming all arguments are objects.
id objPointer;
NSMutableArray *argArray = [NSMutableArray array];
for (int i = 2; i < numberOfArgs; i++) {
[anInvocation getArgument:&objPointer atIndex:i];
[argArray addObject:objPointer];
}
// Now argArray contains the array of all the arguments.
}
@end
The hard part is that you need to make buffers to hold the argument values. If all the arguments are objects or the same type you can use the above code but It's much more complicated to make a generic function if you use C types. You can use NSMethodSignature
's getArgumentTypeAtIndex:
but it returns a string encoding of the type and sizeof wont help you there. You would need to make a map of type names to size_t
s for malloc/calloc.
Edit: I added a concrete example of what I glossed over as // Get the juicy info in methodSignature
As you can see what you want to do is possible but it's pretty tough.
(Check out Apple's documentation on Type Encodings and NSMethodSignature
's signatureWithObjCTypes:
.)
Edit2: This might be better as a separate answer but Here's a complete (and tested) listing of how you can make use of the listing above to make a method that gets called with an arguments array like in JavaScript.
First make a delegate protocol that the Forwarder object will call when a method is called.
@protocol ForwarderDelegate <NSObject>
- (void)selectorCalled:(SEL)selector withArguments:(NSArray *)args;
@end
Then make the actual Forwarder:
@interface Forwarder : NSObject {
@private
NSObject *interfaceObject;
id<ForwarderDelegate> delegate;
}
// Some object whose methods we want to respond to.
@property (nonatomic, retain) NSObject *interfaceObject;
@property (nonatomic, retain) id<ForwarderDelegate> delegate;
@end
@implementation Forwarder
@synthesize interfaceObject;
@synthesize delegate;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [interfaceObject methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
int numberOfArgs = [[anInvocation methodSignature] numberOfArguments];
NSMutableArray *args = [NSMutableArray array];
id ref;
for (int i = 2; i < numberOfArgs; i++) {
[anInvocation getArgument:&ref atIndex:i];
[args addObject:ref];
}
// Call the method on the interface (original) object.
if ([self.interfaceObject respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:self.interfaceObject];
}
[self.delegate selectorCalled:[anInvocation selector] withArguments:args];
}
@end
Now you can instantiate the forwarder that takes some object and forwards any calls to the delegate. If both the target and the delegate are the same object it would work like this:
@interface testreflectAppDelegate : NSObject <UIApplicationDelegate, ForwarderDelegate> {
UIWindow *window;
}
@end
@implementation testreflectAppDelegate
@synthesize window;
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window makeKeyAndVisible];
Forwarder *forwarder = [[[Forwarder alloc] init] autorelease];
forwarder.delegate = self;
forwarder.interfaceObject = self;
[((id)forwarder) doFoo:[NSNumber numberWithInt:1]
withBar:[NSNumber numberWithInt:2]];
return YES;
}
- (void)doFoo:(NSNumber *)foo withBar:(NSNumber *)bar {
NSLog(@"doFoo:withBar: called. Args: %d %d", [foo intValue], [bar intValue]);
}
- (void)doFoo:(NSNumber *)foo {
NSLog(@"doFoo called. Args: %d", [foo intValue]);
}
- (void)selectorCalled:(SEL)selector withArguments:(NSArray *)args {
NSLog(@"selectorCalled: %s with %d arguments", selector, [args count]);
[self doFoo:[args objectAtIndex:0]];
}
@end
Running this should output something like:
testreflect[3098:207] doFoo:withBar: called. Args: 1 2
testreflect[3098:207] selectorCalled: doFoo:withBar: with 2 arguments
testreflect[3098:207] doFoo called. Args: 1
Again this version will only work with id typed arguments. But can work with other types if you use the above mentioned TypeEncodings.
You may want to take a look at the NSMethodSignature class documentation. This class is used to record information on arguments and return values from methods.
精彩评论