使用时:
#import "ViewController.h" #import "LHTimer.h" @interface ViewController () @property (nonatomic, strong) LHTimer *timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = @"Timer VC"; self.view.backgroundColor = [UIColor whiteColor]; self.timer = [[LHTimer alloc] init]; [timer startTimer]; } - (void)dealloc { [self.timer destroyTimer]; }
这里的做法是将原来的timer和VC之间的强引用变成LHTimer和timer之间的强引用,避免timer直接强引用self代表的VC。然而,细心之下你还会发现两个问题:
二次封装提升代码的耦合度
即使封装也要将ti,target,selector,userInfo,repeat这些参数预留出来,供不同地方使用
3)iOS10之后的新API
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
新的API可以不用那么麻烦的去viewWillDisappear里释放,也不用封装,通过__weak弱引用self,可以避免循环引用,这时候就不是NSTimer的问题,而是block怎么避免循环引用了。
4)iOS10之前的API自己改造成block
iOS10之前的API,我们也经常将它封装成一个block形式的API,利用这种方式,可以达到上一条中的效果,当然,要做的事情肯定会多一些,为了详细说明,这里再贴下代码:
#import <Foundation/Foundation.h> @interface NSTimer (LHTimer) + (NSTimer *)LH_ScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval block:(void(^)(void))block repeats:(BOOL)repeats; @end
#import "NSTimer+LHTimer.h" @implementation NSTimer (LHTimer) + (NSTimer *)LH_ScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval block:(void(^)(void))block repeats:(BOOL)repeats { return [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(handle:) userInfo:[block copy] repeats:repeats]; } + (void)handle:(NSTimer *)timer { void(^block)(void) = timer.userInfo; if (block) { block(); } } @end
注意事项:
copy是为防止block在需要的时候已经销毁掉,所以需要拷贝到堆上
使用时记得用__weak避免循环引用
使用该方案需要引入此类头文件
这是一个categroy,categroy可以给系统类添加新方法
5)使用NSProxy增加一个中间层subTarget
其原理是利用NSProxy给NSTimer加入一层stubTarget,stubTarget主要做为一个桥接层,负责NSTimer和target之间的通信。同时NSTimer强引用stubTarget,而stubTarget弱引用target,这样target和NSTimer之间的关系也就是弱引用了,意味着target可以自由的释放,从而解决了循环引用的问题。
这个方式的好处是,当NSTimer的回调函数fireProxyTimer:被执行的时候,会自动判断原target是否已经被释放,如果释放了,意味着NSTimer已经无效,虽然如此,但还是要在不需要的地方进行invalidate操作,只是不需要置nil。
下面看代码:
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface LHProxy : NSObject //通过实例方法创建对象 - (instancetype)initWithObjc:(id)object; //通过类方法创建对象 + (instancetype)proxyWithObjc:(id)object; @end NS_ASSUME_NONNULL_END
#import "LHProxy.h" @interface LHProxy() @property (nonatomic, weak) id subTarget; @end @implementation LHProxy - (instancetype)initWithObjc:(id)object { self.subTarget = object; return self; } + (instancetype)proxyWithObjc:(id)object { return [[self alloc] initWithObjc:object]; } - (void)forwardInvocation:(NSInvocation *)invocation { if ([self.subTarget respondsToSelector:invocation.selector]) { [invocation invokeWithTarget:self.subTarget]; } } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.subTarget methodSignatureForSelector:sel]; } @end
调用:
#import "ViewController1.h" #import "LHProxy.h" @interface ViewController1 () @property (nonatomic, strong)NSTimer *timer; @end @implementation ViewController1 - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor yellowColor]; LHProxy *proxy = [[LHProxy alloc] initWithObjc:self]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(action) userInfo:nil repeats:YES]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissAction)]; [self.view addGestureRecognizer:tap]; } - (void)dismissAction { [self dismissViewControllerAnimated:YES completion:nil]; } - (void)action { NSLog(@"1111111"); } - (void)dealloc { [self.timer invalidate]; } @end
这里是将timer的target对象转移到其他对象,避免真正使用timer的对象被timer强引用(其实源头是被runloop强引用着)。这时,可以在真正使用timer对象的dealloc方法中调用timer的invalidate方法,来解除runloop对timer的强引用,进而释放timer对象。这一点和NSTimer的block用法类似。
总结:NSTimer的问题是在什么时候对其进行invalidate和置nil的问题,普通的NSTimer,放在dealloc内处理,因为self被NSTimer强引用,需要等到NSTimer被invalidate后才能释放,继而执行dealloc方法,而NSTime的invalidate方法则在dealloc内,这就造成了相互等待,无法释放的问题。掌握了这一点,NSTimer就不再再造成内训泄露和循环引用了。这里要格外注意一点,不管是哪种方式,最终都要调用invalidate方法,我们封装或者改造过的NSTimer只是可以不用做置nil操作,同时,可以写在dealloc内。