IOS定时器那点破事(二)

一.开发中如何使用NSTimer


1. self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES];

2. self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];

复制代码

这两个方法是等价的,区别是第一个方法默认创建了一个NSTimer并自动添加到了当前线程的Runloop中去,第二个需要我们手动添加。如果当前线程是主线程的话,某些UI事件,比如UIScrollView的拖拽操作,会将Runloop切换成UITrackingRunLoopMode,这时候,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。所以为了设置一个不会被UI干扰的Timer,我们需要手动将timer的当前RunloopMode设置为NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopMode和UITrackingRunLoopMode的结合。

二.NSTimer无法释放的原因分析

上面的使用方法是没问题的,但是大家在使用过程中一定遇到过因使用了NSTimer,导致所在的UIViewController内存泄漏的问题,这种原因是怎么出现的呢?

其中许多人都认为是UIViewController和NSTimer循环引用的问题,彼此强引用,导致了彼此无法释放,那么问题真的是这样吗?

1.验证如下:

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; 

[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 
//结果:将NSTimer设置成局部变量,你会发现两者仍释放不了。

复制代码

2.将self设置成弱引用,又会是什么现象呢?


__weak typeof(self) weakSelf = self;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:weakSelf selector:@selector(timerFired) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//结果:两者仍然无法释放。
复制代码

结果:其实只有timer单向的指向target,target并未指向timer,是因为timer运行的时候释放不了,导致被强引用的target也无法释放。并非循环引用导致不释放

三.解决NSTimer的内存泄漏问题

一般呢解决NSTimer的内存泄漏问题,通常有两种方法,第一种是找对合适的时机释放NSTimer,通常人们会想到两个调用时机。

-(void)dealloc
 {
    [self.timer invalidate];
 }
//NSTimer,通常人们会想到两个调用时机。
复制代码
-(void)viewWillDisappear:(BOOL)animated { 
    [super viewWillDisappear:animated];
    [self.timer invalidate]; 
} 
//这种情况是可以解决循环引用的问题,内存可以释放,但是又会引来新的问题,当导航控制器push到下一个页面时,当前VC并没有被释放,这时候我们可能并不想销毁NSTimer,我们通常希望VC该销毁的时候,同时销毁NSTimer,所以调用invalidate方法的时机很难找
复制代码

那么就是第二种了,想办法破除强引用,让NSTimer和VC同生共死,这种方法呢也有两种方式:

1.使用block的方式:

 #import <Foundation/Foundation.h>

 typedef void(^JSTimerBlcok)(NSTimer *timer);

 @interface NSTimer (Category)

 + (NSTimer *)js_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval executeBlock:(JSTimerBlcok)block repeats:(BOOL)repeats;

 @end

 #import "NSTimer+Category.h"

 @implementation NSTimer (Category)

 +(NSTimer *)js_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval executeBlock:(JSTimerBlcok)block repeats:(BOOL)repeats

 {

 NSTimer *timer = [self scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(js_executeTimer:) userInfo:[block copy] repeats:repeats];

 return timer;

 }


 +(void)js_executeTimer:(NSTimer *)timer


 {

     JSTimerBlcok block = timer.userInfo;

     if (block) {

     block(timer);

     }

 }

 @end

使用案例: - (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.timer = [NSTimer js_scheduledTimerWithTimeInterval:1.0 executeBlock:^(NSTimer *timer){         __strong typeof(weakSelf) strongSelf = weakSelf;        [strongSelf timerFired:timer];       } repeats:YES]; }

2.使用NSProxy来初始化一个子类,这里我们直接用YYWeakProcy

#import <Foundation/Foundation.h>

 NS_ASSUME_NONNULL_BEGIN


@interface YYWeakProxy : NSProxy

@property (nullable, nonatomic, weak, readonly) id target;

- (instancetype)initWithTarget:(id)target;


+ (instancetype)proxyWithTarget:(id)target;

@end

NS_ASSUME_NONNULL_END


 #import "YYWeakProxy.h"

 @implementation YYWeakProxy

 - (instancetype)initWithTarget:(id)target {

 _target = target;

 return self;

 }

 + (instancetype)proxyWithTarget:(id)target {

 return [[YYWeakProxy alloc] initWithTarget:target];

 }

 - (id)forwardingTargetForSelector:(SEL)selector {

 return _target;

 }

 - (void)forwardInvocation:(NSInvocation *)invocation {

 void *null = NULL;

 [invocation setReturnValue:&null];

 }

 - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {

 return [NSObject instanceMethodSignatureForSelector:@selector(init)];

 }

 - (BOOL)respondsToSelector:(SEL)aSelector {

 return [_target respondsToSelector:aSelector];

 }

 - (BOOL)isEqual:(id)object {
     return [_target isEqual:object];
 }
 
 - (NSUInteger)hash {
     return [_target hash];
 }

 - (Class)superclass {
     return [_target superclass];
 }
 
 - (Class)class {
     return [_target class];
 }
 
 - (BOOL)isKindOfClass:(Class)aClass {
     return [_target isKindOfClass:aClass];
 }
 
 - (BOOL)isMemberOfClass:(Class)aClass {
     return [_target isMemberOfClass:aClass];
 }

 - (BOOL)conformsToProtocol:(Protocol *)aProtocol {

 return [_target conformsToProtocol:aProtocol];

 }
 
 - (BOOL)isProxy {
     return YES;
 }

 - (NSString *)description {
     return [_target description];
 }

 - (NSString *)debugDescription {
     return [_target debugDescription];
 }
 
 @end
 
复制代码

使用案例:


- (void)initTimer {
    YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
    _timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES];
 }
 
复制代码

//至于具体的原理,让NSTimer定时中的方法由YYWeakProxy转发给VC执行.但是NStimer持有的却不是VC.这样就不会循环引用.

猜你喜欢

转载自juejin.im/post/7068225686938812429