2010年8月20日 星期五

在 Project 中加入 SVN version 和 Build Count

為方便加入版本資訊,可使用以下 script,在 Project 的 target 中,right click,New Build Phase,New Run Script Build Phase,之後輸入以下 script,Shell 用 /bin/bash 。
之後可以 #import "SVNRevision.h" 取得 SVN_REVISION 數值。
parse_svnversion()
{
    if [[ "$1" = *:* ]]; then
        arr[0]=${1%:*}
        arr[2]=${1//[0-9:]/}
        tmp_arr[1]=${1#*:}
        arr[1]=${tmp_arr[1]//${arr[2]}/}
    else
        arr[2]=${1//[0-9:]/}
        arr[0]=${1//${arr[2]}/}
        arr[1]=${arr[0]}
    fi

    echo $( (( ${arr[0]} > ${arr[1]} )) && echo ${arr[0]} || echo ${arr[1]} )
}

SVN_REV=$(parse_svnversion `svnversion -n`)
echo "#define SVN_REVISION @\"$SVN_REV\"" > "${PROJECT_DIR}/Classes/SVNRevision.h"
exit 0
parse_svnversion 出自 SiegeX 大大,原文在 StackOverflow 上。

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

2010年6月20日 星期日

Objective-C 神奇的 exchange implementation

在 Objective-C 中,我們可以交換兩個相同 method signature 的 method 內的 implementation,會令 call method A 時實際上運行 method B 的 code,而 call method B 時會運行 method A 的 code。
我們可以利用這個功能,去 "hook" 一些 API,修改其運作方式,對於新手來說這個功能是很危險的,但對於清楚 API 運作的高手來說,這個功能很有用,可以做一些 API 本身沒有的功能。

ExchangeImp.h
@interface NSObject (ExchangeImp)

+ (BOOL)exchangeMethod:(SEL)origSelector withMethod:(SEL)newSelector;

@end

ExchangeImp.m
#import "ExchangeImp.h"

@implementation NSObject (ExchangeImp)

+ (BOOL)exchangeMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
    Method orgMethod = class_getInstanceMethod(self, orgSelector);
    Method newMethod = class_getInstanceMethod(self, newSelector);

    if ((orgMethod != NULL) && (newMethod != NULL))
    {
        if (class_addMethod(self, orgSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        {
            class_replaceMethod(self, newSelector, method_getImplementation(orgMethod), method_getTypeEncoding(orgMethod));
        }
        else
        {
            method_exchangeImplementations(origMethod, newMethod);
        }
        return YES;
    }
    return NO;
}

@end