首页 > 代码库 > object-c编程tips-timer
object-c编程tips-timer
object-c定时器
object-c定时器会自动retain当前的使用者,如果不注意调用invalidate,则很容易引起循环引用导致内存泄露。下面的思路提供了一套还算可行的解决方案。
举例:
经常在viewController中有可能有自动刷新界面的需求。 获取数据失败后,每隔10秒自动刷新重新获取数据,这个时候使用NSTimer是一个很方便的事情。一般情况下直接创建一个NSTimer的repeat对象,然后实现对应的timerFireMethod方法。 当用户主动点击返回按钮时候,此界面应该被释放,但是由于NSTimer retain了当前的viewController,导致界面内存泄露。 你可能会说在dealloc中调用invalidate,但是必须明白dealloc根本就不会调用,当然viewDidDisappear也一样不会被调用。
前一段时间看了effective object-c,学习了一种很好的思想,现分享出来。
给NSTimer添加一个类别,使用block的方式传递timerFireMethod,代码如下:
@implementation NSTimer(LPBLocks) +(NSTimer*) lpScheduleTimerWithTimerInternal:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats { return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(lpTimerBlockInvoke:) userInfo:[block copy] repeats:repeats]; } +(void)lpTimerBlockInvoke:(NSTimer*)timer { void(^block)() = timer.userInfo; if(block){ block(); } } @end
这个scheduledTimer方法也会retain target,但是由于这是一个类方法,它保留的是类对象,因此也就不会有什么问题。 它传入要执行的block, 然后在回调函数中通过userInfo得到block,并执行。
改进:
这个已经是一个很大的改进了,我们可以在代码中放心的传入block代码。不过仔细思考一下,如果在block中引入了viewController的成员,而且timer又作为成员变量存在于viewController中。
例如如下的代码:
@interface LPNextViewController () { NSTimer* refreshTimer; }
这样viewController和refreshTimer又陷入了循环引用的逻辑圈里。当然可以在block中使用weak_self的方式避免循环引用,但是写起代码来总是有些不顺手,而且还必须要外部使用者显式的进行。
于是很容易想到,应该封装到一个专门的LPTimer类中。它负责持有NSTimer,同时NSTimer的block使用LPTimer的weak版本。
@interface LPTimer () { NSTimer* _pollTimer; //timer selector __weak id _weak_target; SEL _selector; id _userInfo; } @end
-(void)scheduleTimerWithTimerInternal:(NSTimeInterval)interval target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats { __weak id weak_self = self; _weak_target = target; _selector = aSelector; _userInfo = userInfo; //借用第一个版本的block思想 //使用了第二层间接,调用_weak_target的aSelector方法。 //这样可以把stopTimer给封装进去,外部不需要管理timer的stop。 _pollTimer = [NSTimer lpScheduleTimerWithTimerInternal:1 block:^{ [weak_self doTimer]; } repeats:repeats]; }
上面的代码LPTimer持有NSTimer对象,而NSTimer执行的block使用的是weak_self。 它在timer触发的时候调用自身的doTimer方法。在doTimer中负责将方法传递给外部的使用者。
-(void)doTimer { if ([_weak_target respondsToSelector:_selector]) { [_weak_target performSelector:_selector withObject:self]; } else{ DLog(@"WARNNING: unknown selector"); } }
_weak_target是外部的使用者。 外部的使用者可以将LPTimer看成是一个普通的对象就行,持有它也不会有什么问题。 LPTimer保留一个弱引用指向外部的使用者。在时间到timer触发的时候,会先调到NStimer的block中,然后传递到LPTimer的doTimer中,然后调用到_weak_target的selector中。
必须注意释放NStimer对象,在LPTimer释放的时候调用NSTimer的invalidate方法。
-(void)stopTimer { DLog(@""); [_pollTimer invalidate]; } -(void)dealloc { [self stopTimer]; DLog(@""); }
总结:
基本的思想就是NSTimer会retain一个对象,现在让它retain类对象。 当时候到来进行触发的时候,由NSTimer类对象触发到Block中,继而触发到外部的LPTimer普通对象中。在普通对象中我们就可以自由的进行处理了。使用weak_target使LPTimer弱引用外部使用者,断开外部使用者与LPTimer的关联。 使用weak_self断开LPTimer与NStimer的循环关联。 个人认为还算不错的思想, 有需要的欢迎讨论。