ios中的定时器

710 阅读3分钟

NSTimer

@interface TwoViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) UILabel *textLabel;
@end

- (void)testTimer1 {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateTime) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    [timer fire];
    _timer = timer;
}

- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}

上面造成的结果是 dealloc 永远无法执行,timer不会被释放,有人会说把属性中的timer 改成弱引用,测试后还是不行,原因是什么呢? 我们发现UIViewController 无论是否强弱引用着 timer 都会造成, timer都会强引用者UIViewController。核心原因是因为下面这个引用链。

NSRunLoop --->NSTimer ---> UIViewController

无论UIViewController 是否强引用者timer,我们发现上面的引用链都会存在,一般我们都是在主线程用的NSTimer。

如何解决呢?

  1. 使用不带target的 block 防止出现控制器无法释放的问题,使用target会导致这个问题,那我不使用target这个api问题不解决了吗?代码如下
 __weak typeof(self) weakSelf = self;
    NSTimer *timer2 = [NSTimer timerWithTimeInterval:5 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"我是timer2");
        [weakSelf updateTime];
    }];
    
    //需要主动加入 runLoop 中
    [[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSRunLoopCommonModes];
    [timer2 fire];
    _timer = timer2;
- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}

2.使用target,不让target直接引用这控制器,其中这个里面有两种处理方式:

a. 使用NSProxy来解决这个问题

@interface TTRealProxy : NSProxy
+ (TTRealProxy *)proxyWithTarget:(id)target;
@end
@interface TTRealProxy ()
@property (nonatomic, weak) id target;
@end

@implementation TTRealProxy

+ (TTRealProxy *)proxyWithTarget:(id)target {
    TTRealProxy *realProxy = [TTRealProxy alloc];
    realProxy.target = target;
    return realProxy;
}


- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [_target methodSignatureForSelector:sel];
}


- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:_target];
}


@end

具体使用如下:

  NSTimer *timer3 = [NSTimer scheduledTimerWithTimeInterval:1 target:[TTRealProxy proxyWithTarget:self] selector:@selector(updateTime) userInfo:nil repeats:YES];
    [timer3 fire];
    _timer = timer3;
- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}

b.使用类对象来充当target,其实这个最终处理的写法最终有点像 1中的解决方案了

@interface NSTimer (LWZAddtion)

+ (NSTimer *)LWZ_TimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer * _Nonnull))block;
- (void)lwz_fire;
@end
#import "NSTimer+LWZAddtion.h"
#import <objc/message.h>

@implementation NSTimer (LWZAddtion)

+ (void)lwz_PerformBlockWithTimer:(NSTimer *)timer {
    if(timer.lwz_usingBlock != nil) timer.lwz_usingBlock(timer);
}

+ (NSTimer *)LWZ_TimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer * _Nonnull))block {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(lwz_PerformBlockWithTimer:) userInfo:nil repeats:repeats];
    [timer set_LwzUsingBlock:block];
    return timer;
}


- (void)set_LwzUsingBlock:(void (^)(NSTimer * _Nonnull))usingBlock {
    objc_setAssociatedObject(self, @selector(lwz_usingBlock), usingBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (void (^)(NSTimer * _Nonnull))lwz_usingBlock {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)lwz_fire {
    [self setFireDate:[NSDate dateWithTimeIntervalSinceNow:self.timeInterval]];
}

真正的使用

 - (void)testTimer4 {

    __weak typeof(self) weakSelf = self;

    NSTimer *timer4 = [NSTimer LWZ_TimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {

        __strong typeof(weakSelf) strongSelf = weakSelf;

        if (strongSelf == nil) {

            [strongSelf.timer invalidate];

            return;

        }

        [strongSelf updateTime];

        

    }];

    [timer4 lwz_fire];

    

    _timer = timer4;

}




- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}

CADisplayLink

CADisplayLink 的问题和 NSTimer 产生的循环引用的问题原因是一样的,解决方案也类似

CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[TTRealProxy proxyWithTarget:self] selector:@selector(updateTime)];
    [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    if(@available(ios 10.0 ,*)){
        link.preferredFramesPerSecond = 1;
    }else{
        link.frameInterval = 60;
    }
    _link = link;
- (void)dealloc {    
    [self.link invalidate];
    self.link = nil;
}

GCD定时器:当我们需要定时器非常准的时候,可以用这个,前两个和runloop有关系,不论怎么样都可能不准。

代码如下:


@interface TGCDTimer : NSObject
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat :(BOOL)repeat queue:(dispatch_queue_t) queue block:(dispatch_block_t) block;
- (void)stop;
- (void)restart;
- (void)invalidate;
@end


#import "TGCDTimer.h"

@interface TGCDTimer() {
    dispatch_source_t _timer;
    BOOL _isFire;
}
@end

@implementation TGCDTimer

- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat :(BOOL)repeat queue:(dispatch_queue_t) queue block:(dispatch_block_t) block {
    //队列必须存在
    if(queue == NULL) return nil;
    
    if(self = [super init]) {
        // 1.创建定时器
        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        
        // 2.设置定时器的时间
        dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval * NSEC_PER_SEC, 0);
        
        // 3.监听定时器的回调
        dispatch_source_set_event_handler(_timer, ^{
            //执行Block
            if(block) {
                block();
            }
            
            //如果不重复,执行一次后销毁
            if (!repeat) {
                self->_isFire = NO;
                dispatch_source_cancel(self->_timer);
            }
           
        });
        
        //在相等的间隔后调起 timer
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            dispatch_resume(self -> _timer);
            self->_isFire = YES;
        });
        
    }
    return self;
}


- (void)stop {
    if(_isFire){
        _isFire = NO;
        dispatch_suspend(self->_timer);
    }
}


- (void)restart {
    if (!_isFire) {
        _isFire = YES;
        dispatch_resume(self->_timer);
    }
}


- (void)invalidate {
    _isFire = NO;
    dispatch_source_cancel(self->_timer);
}

- (void)dealloc {
    _isFire = NO;
    dispatch_cancel(self->_timer);
}



@end

具体使用

- (void)testTimer5 {
    __weak typeof(self) weakSelf = self;
    TGCDTimer *timer = [[TGCDTimer alloc]initWithTimeInterval:1 repeat:YES queue:dispatch_get_main_queue() block:^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf == nil) {
            [strongSelf.gcdTimer invalidate];
        }
        NSLog(@"我是timer5");
        [strongSelf updateTime];
        [strongSelf freez];
    }];
    self.gcdTimer = timer;
}
    
    
    - (void)dealloc {
    //停止GCD 定时器
    [self.gcdTimer invalidate];
}