Target-Action模式在iOS中的应用

 由于简单易用,尤其是相对于Delegate模式,Target-Action模式得到了广泛的应用,包括派生自UIControl的UIButton,UISlider等视图类,以及NSTimer, UIGestureRecognizer等Event Sources类。

 Target-Action的主要优势就在于其的简单好用,当需要接收某个Object的的特定的事件时,只需发送以下消息即可,

[Object addTarget:target action:@selector(eventHappend:) forControlEvents:]

响应事件的对象只需实现eventHappend:方法即可。这里有人可能会问:可以为Action指定多个参数吗?有些情况会有传递多个参数的需要。确实有时候有传递多个参数的需要,有些接口确实需要比较多的信息。这里,iOS为了保持其作为一个设计模式的简洁特性,且由于其广泛的使用,有必要保证其易理解易传播易使用。因此,iOS做了以下规定:

原则上支持这三种形式的Action。常见的情形:UIButton的Action为第二种,参数类型为UIButton *,UIGestureRecognizer的Action也为第二种,参数类型为UIGestureRecognizer *。因此,如果你有多个参数需要传递,可以通过派生Action的发送方的方式实现消息的传递,即派生UIButton等类。如果使用的是自定义的控件,即派生自UIControl或者UIView,则可以自定义Action的参数类型,将要传递的多个参数组合为一个自定义的类,从而满足Action对参数个数的要求。

 不同于Delegate的一对一的消息传递,也不同于Notification的一对多的消息传递。Target-Action既支持简单的一对一消息传递,另外根据控件类型的不同,可以为不同的事件类型指定不同的Target-Action组合。比如有一个Button,同时响应UIControlEventTouchDown和UIControlEventTouchUpInside两种事件,并且希望将这两种不同的事件发往不同的Target,这对于Delegate几乎是不可能的任务,而对于Button则可以轻松实现,如下:

[Object addTarget:target1 action:@selector(touchDown:) forControlEvents: UIControlEventTouchDown];
[Object addTarget:target2 action:@selector(touchUp:) forControlEvents: UIControlEventTouchUpInside];

 由于Objective-C采用引用计数管理对象的生命周期,所以为了避免循环引用导致对象无法释放,最终导致内存泄漏,这里Target-Action中的Target通常不会被retain的,所以这就需要我们保证Target不被过早地释放。但是,这里有一个特例NSTimer, 其机制类似于延时触发,为了保证timer耗尽时target还未被释放,NSTimer会持有Target直到NSTimer收到invalidate消息。这一点尤其需要注意,大部分的Target都不会被持有,只有NSTimer 除外,因为其延时触发的机制所需要。另外,对于一个repeat NSTimer在不需要时,一定要调用NSTimer的invalidate消息,否则target不会被释放。此外,调用invalidate消息的地方也很重要,通常是在Action方法中调用invalidate消息,有些同学可能会在Target的dealloc方法中调用NSTimer的invalidate消息,这样是会出问题的,由于NSTimer持有Target,所以只有NSTimer释放之后才有机会调用Target的dealloc方法,这钟写法将导致内存泄漏。另外一点需要注意的是,为了避免Target的过早释放,NSTimer中发送Action消息的方法如下:

- (void)sendAction {
    [target retain];
    [target perform:@selector(timerFired:) withObject:self];
    [target release];
}

- (void)timerFired:(NSTimer *)timer {
    [timer invalidate];    
    //work code    
}

为什么在发送timerFired:消息之前和之后分别要调用Target的retain和release方法呢?关键就在于timerFired:方法中会调用timer的invalidate方法,该方法会将timer从runLoop中移除,并将向Target发送release消息,这时timer可能是唯一一个持有Target的对象,可能会导致Target的过早释放,从而导致崩溃。因此,需要在发送timerFired:消息之前和之后分别调用retain和release方法。

    可以看出,Target-Action简单好用,应用场景也很广泛,不需要太多的额外工作。一个潜在的问题可能是其参数个数比较固定,可能不够灵活,当然这也是降低复杂度的一个代价,在这种情形下可能反而促使我们更多的进行封装和抽象,隐藏不必要的实现细节,从而更好地设计代码。大多数情形下Target都只是进行简单的assign操作,只有NSTimer需要引起特别的注意,因为NSTimer会持有Target对象。

posted @ 2013-12-30 15:56  CoderZHY  阅读(1720)  评论(0编辑  收藏  举报