前些天接了一个SDK小项目,到手时候已经是那个开发小哥快要闪人的时候,而这项项目也正是刚刚提测之际,这种酸爽,你懂得~ 测试MM一天测出来一个bug,打开工程一看,我的天定时器问题,居然又是NSTimer~搞了一下午,搞好了问题,接下来就是总结一下
至于NSTimer的用法倒是很简单,不过初次应用很难不会遇到各种闪退,各种疑难杂症,究其原因主要还是循环引用在搞事情。下面我就避免NSTimer在日常开发中避免循环引用我知道的4种用法做一总结
大神可以绕到了,勿喷
1、分类加block
这种方法我是最先知道也最早用的,因为本人向来对分类就有一种莫名的好感,一看是就在用这种方法
#import <Foundation/Foundation.h>
@interface NSTimer (UnRetainTimer)
+ (NSTimer *)az_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval
repeats:(BOOL)repeats
block:(void(^)(NSTimer *timer))block;
@end
#import "NSTimer+UnRetainTimer.h"
@implementation NSTimer (UnRetainTimer)
+ (NSTimer *)az_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block{
return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(az_blcokInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)az_blcokInvoke:(NSTimer *)timer {
void (^block)(NSTimer *timer) = timer.userInfo;
if (block) {
block(timer);
}
}
@end
使用这种方案可以防止timer对类的保留,从而打破了循环引用的产生。但是这种方法在运用的时候要注意一点(敲黑板了~~)
__weak Test1ViewController *weakSelf = self;
_timer = [NSTimer az_scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer *timer) {
__strong Test1ViewController *strongSelf = weakSelf;
[strongSelf doSomeThing];
// [self doSomeThing];
}];
这几行代码一定要注意了,一定要这么写,先搞个weakSelf,然后在strong一下,这样能防止执行块的代码时,类被释放了还不会应用到self。我还试了试,要是这里还用self去调用doSomeThing的话依然会循环引用。最后就是记得在类的dealloc方法中,调用[_timer invalidate]。
#import "Test1ViewController.h"
#import "NSTimer+UnRetainTimer.h"
@interface Test1ViewController (){
}
@property (nonatomic,strong) NSTimer *timer;
@property (nonatomic) NSInteger count;
@end
@implementation Test1ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
__weak Test1ViewController *weakSelf = self;
_timer = [NSTimer az_scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer *timer) {
__strong Test1ViewController *strongSelf = weakSelf;
[strongSelf doSomeThing];
// [self doSomeThing];
}];
}
- (void)doSomeThing {
NSLog(@"%ld",(long)self.count++);
}
- (void)dealloc {
[self.timer invalidate];
}
@end
2、用NSProxy做代理
有NSProxy这么一个好用的根类,NSProxy与NSObject一样是根类,都遵守协议。 它实质上是通过一个代理的模式,从而巧妙的绕开了循环引用的怪圈。
#import <Foundation/Foundation.h>
@interface AZTimerProxy : NSProxy
@property (nonatomic,weak) id obj;
@end
#import "AZTimerProxy.h"
@implementation AZTimerProxy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig;
sig = [self.obj methodSignatureForSelector:aSelector];
return sig;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.obj];
}
@end
#import "TestViewController.h"
#import "AZTimerProxy.h"
@interface TestViewController (){
}
@property (nonatomic,strong) AZTimerProxy *proxy;
@property (nonatomic,strong) NSTimer *timer;
@property (nonatomic) NSInteger count;
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
self.proxy = [AZTimerProxy alloc];
self.proxy.obj = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(doSomeThing) userInfo:nil repeats:YES];
}
- (void)doSomeThing {
NSLog(@"%ld",(long)self.count++);
}
- (void)dealloc {
[self.timer invalidate];
}
@end
3、runtime 这个方法主要是参考了大神的方法,在这里做一下列举,可以参考膜拜下。传送门在此 看起来原理跟上一个方法一样,鉴于runtime的理解比起大神还是差之千里,我就简单的验证了一下大神的方法,并做了一下搬运工了~
只是我自己感觉这个方法有些繁琐啊~
#import "Test2ViewController.h"
#import <objc/runtime.h>
@interface Test2ViewController ()
@property (nonatomic,strong) id timerTarget;
@end
static const void * TimerKey = @"TimerKey";
static const void * weakKey = @"weakKey";
@implementation Test2ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// Do any additional setup after loading the view.
//初始化timerTarge对象
_timerTarget = [NSObject new];
//动态创建timerEvent方法
class_addMethod([_timerTarget class], @selector(timerEvent), (IMP)timMethod, "v@:");
//创建计时器target对象为_timerTarget
NSTimer *_timer;
_timer = [NSTimer timerWithTimeInterval:1 target:_timerTarget selector:@selector(timerEvent) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
//将self对象与NSTimer对象与_timerTarget对象关联
objc_setAssociatedObject(_timerTarget, TimerKey, _timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(_timerTarget, weakKey, self, OBJC_ASSOCIATION_ASSIGN);
}
void timMethod(id self,SEL _cmd)
{
Test2ViewController *vc = objc_getAssociatedObject(self, weakKey);
[vc performSelector:_cmd];
}
-(void)timerEvent
{
NSLog(@"%@",NSStringFromClass([self class]));
}
-(void)dealloc
{
NSTimer *timer = objc_getAssociatedObject(_timerTarget, TimerKey);
[timer invalidate];
NSLog(@"%@--dealloc",NSStringFromClass([self class]));
}
@end
4、MSWeakTimer
这个方法看起来屌啊,不过我在项目中还没用过,我试着用了用还是挺不错的,支持pod,用着方便!传送门在此
我这里是直接将MSWeakTimer文件夹拖到工程里面。等到过几天有时间了再来研究一把源码了
#import "Test3ViewController.h"
#import "MSWeakTimer.h"
@interface Test3ViewController ()
@property (nonatomic,strong) MSWeakTimer *timer;
@property (nonatomic) NSInteger count;
@end
@implementation Test3ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//大神说了,这个可以在任意的线程里面去用啊,这里我就试了试main queue和global queue
// dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
self.view.backgroundColor = [UIColor whiteColor];
_timer = [MSWeakTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES dispatchQueue:queue];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)dealloc {
NSLog(@"我销毁了");
}
- (void)doSomeThing {
NSLog(@"%ld",(long)self.count++);
}
@end
5、苹果爸爸iOS 10 以后提供的新方法
听说苹果爸爸提供了新的方法来方便我的解决内存泄漏的问题,我急忙点开NSTimer类的头文件,看了一波。但由于这个要支持iOS 10 以后,而我们的项目才刚刚说服领导去放弃iOS7 的支持,所以这个方法我自己先积累到这里了,等以后了可以搞起来~~~
+ (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));
好奇心还是叫我试了试
#import "Test4ViewController.h"
@interface Test4ViewController ()
@property (nonatomic,strong) NSTimer *timer;
@property (nonatomic,assign) NSInteger count;
@end
@implementation Test4ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
__weak Test4ViewController *weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf countTimer];
}];
}
- (void)countTimer{
NSLog(@"--->%ld",(long)self.count++);
}
- (void)dealloc{
[_timer invalidate];
}