2011年10月31日 星期一

如何把Target/Action放進array裡面呢?

Target-Action相信這個design pattern對於寫ios一陣子的人都不陌生
這是幾個ios fundamental design pattern之一
然而有時候我們會希望把幾個target action放進array或是dictionary之中,或是直接當作member variable
但是SEL並不是一般繼承於NSObject的class,所以不能直接放進Array當中
當然我們可以用
NSStringFromSelector(SEL aSelector)
以及
NSSelectorFromString(NSString *aSelectorName)
讓SEL可以跟NSString*互換
但是怎麼都覺得不是很方便
我就在想有沒有比較漂亮的方法

我找了一下iOS foundation library,當中就屬NSInvocation最合適
這個Class就是把target, action, arguments包在一起,
當我們呼叫-[NSInvocation invoke]
就會呼叫這個target的selector,並且傳進這些arguments...
太棒了,這就是我要的class!!

但是NSInvocation的產生方式實在不怎麼好用,如果我們要把這些東西全部丟進去
那我們需要如下面這段code
NSMethodSignature* sig = 
    [[target class] instanceMethodSignatureForSelector:@selector(mySelector:];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setTarget:target];
[invocation setSelector:selector];
看起來很不友善吧...

不好用的話,那我們就來自己動手做Category吧...

NSInvocation+PopcornyLu.h / NSInvocation+PopcornyLu.m


這邊我提供了兩個Class messages來產生NSInvocation
其中第二個可以提供不定量的arguments.
使用上很簡單
// Put invocations in array
NSArray* myActions =
    [NSArray arrayWithObjects:
        [NSInvocation invocationWithTarget:self
                                  selector:@selector(action1)],
        [NSInvocation invocationWithTarget:self
                                  selector:@selector(action2:)
                                 arguments:@"foo", nil],
        [NSInvocation invocationWithTarget:self
                                  selector:@selector(action3WithFoo:andBar:)
                                 arguments:@"foo", @"bar", nil],
        nil];

// invoke all the actions
for(NSInvocation* invocation in myActions)
{
    [invocation invoke];
}

[補充]
當然也可以用block來取代target/action
以上面的例子我們可以用block來實作
NSArray* myActions2 = 
 [NSArray arrayWithObjects:
  
 [[^(void){
  [self action1];
 } copy] autorelease],
  
 [[^(void){
  [self action2:@"foo"];
 } copy] autorelease],         
  
 [[^(void){
  [self action3WithFoo:@"foo" andBar:@"bar"];
 } copy] autorelease],            
  
 nil];        

// invoke all the actions
for(void(^invocation)() in myActions2)
{
    invocation();
}

但是缺點是code比較凌亂一點,然後action1, action2:, action3WithFoo:withBar這些message都要在前面要先定義
稍微麻煩了點
但基本上Target/Action或是Block都有其方便之處
像這邊我希望array creation的地方不要寫太多code,所以我會選擇用target/action
但有時候使用上會用inline block比較方便,那我就用block