2010年6月22日 星期二

NSInvocation 用在 class method 上

為了能簡單地調用 NSInvocation 去 invoke class method (+),特意寫了一個 HWInvocation,是 NSInvocation 的 category,加入了一個新的 constructor。寫完後也順手寫了 instance method (-) 版本的 constructor。
HWInvocation 利用了 Objective-C 的 Variable Arguments 功能,不用 call 無數個 setArgument,可以在 constructor 中一次過輸入所有 arguments。
另外 Sample Code 中示範了怎樣傳 NSObject 和非 NSObject 類變數到 NSInvocation 中,其秘訣是使用 address of (&) 去取得 memory address (pointer)。

SampleCode.m
#import "HWInvocation.h"

// ... other code skipped ...

+ (void)setSomething:(BOOL)yesOrNo str:(NSString *)string
{
 NSLog(@"setSomething: %@ - %@",(yesOrNo == YES ? @"YES" : @"NO"),string);
}

- (void)setOtherThing:(int)num str:(NSString *)string
{
 NSLog(@"setOtherThing: %d - %@",num,string);
}

- testInvocation {
 NSString *str = @"Testing HWInvocation";
 BOOL yes = YES;
 int num = 123;
 NSInvocation *invocation;
 
 invocation = [NSInvocation classMethodInvocationWithSelectorName:@"setSomething:str:" baseClass:[SampleCode class] args:&yes,&str];
 [invocation tryInvoke];
 
 invocation = [NSInvocation invocationWithSelectorName:@"setOtherThing:str:" target:self args:&num,&str];
 [invocation tryInvoke];
}

HWInvocation.h
#import <Foundation/Foundation.h>

@interface NSInvocation (HWInvocation)

+ (NSInvocation *)classMethodInvocationWithSelectorName:(NSString *)selname baseClass:(Class)class args:(void *)buffer, ...;
+ (NSInvocation *)invocationWithSelectorName:(NSString *)selname target:(id)target args:(void *)buffer, ...;
- (BOOL)tryInvoke;

@end
HWInvocation.m
#import "HWInvocation.h"
#import <objc/runtime.h> // Needed for the ISA pointer

@implementation NSInvocation (HWInvocation)

+ (NSInvocation *)classMethodInvocationWithSelectorName:(NSString *)selname baseClass:(Class)class args:(void *)buffer, ...
{
 SEL aSelector;
 NSMethodSignature *signature;
 NSInvocation *invocation;
 int numOfArgs;
 int i = 3; // Index to place the first variable argument
 void *buf;
 va_list argumentList;
 
 aSelector = NSSelectorFromString(selname);
 if (aSelector == NULL) return nil;
 
 signature = [class->isa instanceMethodSignatureForSelector:aSelector];
 if (signature == nil) return nil;
 
 numOfArgs = [signature numberOfArguments];
 
 invocation = [NSInvocation invocationWithMethodSignature:signature];
 if (invocation == nil) return nil;
 
 [invocation setTarget:class];
 [invocation setSelector:aSelector];
 
 if (numOfArgs >= i)
 {
  [invocation setArgument:buffer atIndex:2];
  va_start(argumentList, buffer);
  while (i < numOfArgs)
  {
   buf = va_arg(argumentList, id);
   [invocation setArgument:buf atIndex:i++];
  }
  va_end(argumentList);
 }
 
 return invocation;
}

+ (NSInvocation *)invocationWithSelectorName:(NSString *)selname target:(id)target args:(void *)buffer, ...
{
 SEL aSelector;
 NSMethodSignature *signature;
 NSInvocation *invocation;
 int numOfArgs;
 int i = 3; // Index to place the first variable argument
 void *buf;
 va_list argumentList;
 
 aSelector = NSSelectorFromString(selname);
 if (aSelector == NULL) return nil;
 
 signature = [target methodSignatureForSelector:aSelector];
 if (signature == nil) return nil;
 
 numOfArgs = [signature numberOfArguments];
 
 invocation = [NSInvocation invocationWithMethodSignature:signature];
 if (invocation == nil) return nil;
 
 [invocation setTarget:target];
 [invocation setSelector:aSelector];
 
 if (numOfArgs >= i)
 {
  [invocation setArgument:buffer atIndex:2];
  va_start(argumentList, buffer);
  while (i < numOfArgs)
  {
   buf = va_arg(argumentList, id);
   [invocation setArgument:buf atIndex:i++];
  }
  va_end(argumentList);
 }
 
 return invocation;
}

- (BOOL)tryInvoke
{
 @try
 {
  [self invoke];
  return YES;
 }
 @catch (NSException * e)
 {
  NSLog(@"Invoke failed: %@",NSStringFromSelector([self selector]));
  return NO;
 }
 return NO;
}

@end

沒有留言:

張貼留言