JHHK

欢迎来到我的个人网站
行者常至 为者常成

定时器存在的问题

目录

定时器的循环引用

一、如何产生了循环引用

@interface ViewController ()
@property (nonatomic, strong) NSTimer * timer;
@property (strong, nonatomic) CADisplayLink *link;
@end

@implementation ViewController

- (void)viewDidLoad{
    [super viewDidLoad];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:self
                                                selector:@selector(test)
                                                userInfo:nil
                                                 repeats:YES];

    // 保证调用频率和屏幕的刷帧频率一致,60FPS
    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

}

-(void)test{
    NSLog(@"定时器调用");
}

@end

上面的两个定时器存在强引用问题,self强引用timer,timer相关接口要求传入self,并且在合适的时间向self发送消息@selector(test),对self也是强引用。这样就会造成循环引用问题,导致内存泄漏。

二、如何解决

1、方式一:使用block

self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                             repeats:YES
                                               block:^(NSTimer * _Nonnull timer) {
    NSLog(@"定时器调用");
}];

2、使用代理破坏循环引用结构

创建一个代理类MJProxy

MJProxy.h文件

#import <Foundation/Foundation.h>

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

MJProxy.m文件

#import "MJProxy.h"

@implementation MJProxy

+ (instancetype)proxyWithTarget:(id)target
{
    // NSProxy对象不需要调用init,因为它本来就没有init方法
    MJProxy *proxy = [MJProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}
@end

调用

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

调整之后指针的指向及强弱引用如下:

img

定时器的偏差问题

NSTimer依赖于RunLoop,如果RunLoop任务过于繁重,可能导致NSTimer不准时

而GCD的定时器更加准时

- (void)test{
    
    // 队列
    //    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_queue_t queue = dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL);
    
    // 创建定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 设置时间
    uint64_t start = 2.0; // 2秒后开始执行
    uint64_t interval = 1.0; // 每隔1秒执行
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
                              interval * NSEC_PER_SEC, 0);
    
    // 设置回调
    //    dispatch_source_set_event_handler(timer, ^{
    //        NSLog(@"1111");
    //    });
    dispatch_source_set_event_handler_f(timer, timerFire);
    
    // 启动定时器
    dispatch_resume(timer);
    
    self.timer = timer;
}

void timerFire(void *param){
    NSLog(@"2222 - %@", [NSThread currentThread]);
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.