美文网首页
解决NSTimer循环引用、实现高效计时器

解决NSTimer循环引用、实现高效计时器

作者: 晨阳Xia | 来源:发表于2021-02-19 10:14 被阅读0次

    解决NSTimer循环引用的两个方法

    1、系统自带方法

    @property (strong, nonatomic) NSTimer *timer;
    
    __weak typeof(self)weakSelf = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            [weakSelf timerTest];
    }];
    
    - (void)timerTest {
        NSLog(@"%s",__func__);
    }
    
    - (void)dealloc{
        [self.timer invalidate];
    
    }
    

    2、创建中间target

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface XSYTimerProxy : NSProxy
    
    + (id)proxyWithTarget:(NSObject *)target;
    @property (weak, nonatomic) id target;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "XSYTimerProxy.h"
    
    @implementation XSYTimerProxy
    
    + (id)proxyWithTarget:(NSObject *)target {
        XSYTimerProxy *proxy = [XSYTimerProxy alloc];
        proxy.target = target;
        return proxy;
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        return  [self.target methodSignatureForSelector:sel];
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        [invocation invokeWithTarget:self.target];
    }
    
    @end
    
    #import "XSYTestViewController.h"
    #import "XSYTimerObject.h"
    
    @interface XSYTestViewController ()
    
    @property (strong, nonatomic) NSTimer *timer;
    
    @end
    
    @implementation XSYTestViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[XSYTimerObject timerObjectWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
      
    }
    
    - (void)timerTest {
        NSLog(@"%s",__func__);
    }
    
    - (void)dealloc{
        [self.timer invalidate];
    }
    
    @end
    

    NSTimer 和 CADisplayLink

    基于runloop,如果任务太多runloop的循环会有导致定时器不准时
    使用gcd保证定时器的准时

    gcd实现计时器

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface XSYGCDTimer : NSObject
    
    
    /// 开启定时器
    /// @param task 任务
    /// @param start 多长时间后开始
    /// @param interval 重复时间
    /// @param repeats 是否重复
    /// @param async 是否在子线程中执行
    + (NSString *)scheduledTimerTask:(void(^)(void))task
                              starts:(NSTimeInterval)start
                            interval:(NSTimeInterval)interval
                             repeats:(BOOL)repeats
                               async:(BOOL)async;
    
    
    /// 开启定时器
    /// @param target target
    /// @param selector 方法选择器
    /// @param start 多长时间后开始
    /// @param interval 重复时间
    /// @param repeats 是否重复
    /// @param async 是否在子线程中执行
    + (NSString *)scheduledTimerTarget:(id)target
                              selector:(SEL)selector
                                starts:(NSTimeInterval)start
                              interval:(NSTimeInterval)interval
                               repeats:(BOOL)repeats
                                 async:(BOOL)async;
    
    
    /// 取消定时器
    /// @param task 任务
    + (void)cancelTask:(NSString *)task;
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    #import "XSYGCDTimer.h"
    
    @implementation XSYGCDTimer
    
    static NSMutableDictionary *timers;
    dispatch_semaphore_t semaphore_;
    
    + (void)initialize {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            timers = [[NSMutableDictionary alloc] init];
            semaphore_ = dispatch_semaphore_create(1);
        });
    }
    
    + (NSString *)scheduledTimerTask:(void (^)(void))task starts:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async {
        if (!task || start < 0 || (interval <= 0 && repeats)) {
            return nil;
        }
        
        dispatch_queue_t queue = async ? dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL) : dispatch_get_main_queue();
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
        
        // 加锁操作
        dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
        // 任务名称
        NSString *taskName = [NSString stringWithFormat:@"%ld",timers.count];
        // 将执行中的任务加入字典
        timers[taskName] = timer;
        dispatch_semaphore_signal(semaphore_);
        
        dispatch_source_set_event_handler(timer, ^{
            task();
            if (!repeats) {
                [self cancelTask:taskName];
            }
        });
        
        dispatch_resume(timer);
        
        return taskName;
    }
    
    + (NSString *)scheduledTimerTarget:(id)target selector:(SEL)selector starts:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async {
        return [XSYGCDTimer scheduledTimerTask:^{
            if ([target respondsToSelector:selector]) {
                // clang 是对应的编译器,根据需要可以改成 GCC 等
                #pragma clang diagnostic push
                #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                //写在这个中间的代码,都不会被编译器提示-Wdeprecated-declarations类型的警告
                // 如果同时要忽略其他类型的警告,只需要继续添加  #pragma clang diagnostic ignored 即可
                [target performSelector:selector];
                #pragma clang diagnostic pop
            }
        } starts:start interval:interval repeats:repeats async:async];
    }
    
    /** 取消任务 */
    + (void)cancelTask:(NSString *)task {
        if (task.length <= 0) {
            return;
        }
        
        dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
        dispatch_source_t timer = timers[task];
        if (timer) {
            dispatch_cancel(timers[task]);
            [timers removeObjectForKey:task];
        }
        dispatch_semaphore_signal(semaphore_);
        
    }
    
    @end
    
        
    

    使用

    #import "ViewController.h"
    #import "XSYGCDTimer.h"
    
    @interface ViewController ()
    
    @property (strong, nonatomic) NSString *task;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
    //    self.task = [XSYGCDTimer scheduledTimerTask:^{
    //        NSLog(@"%@", [NSThread currentThread]);
    //    } starts:2.0 interval:1.0 repeats:YES async:YES];
        self.task = [XSYGCDTimer scheduledTimerTarget:self selector:@selector(timerTest) starts:2.0 interval:1.0 repeats:YES async:YES];
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [XSYGCDTimer cancelTask:self.task];
    }
    
    - (void)timerTest {
        NSLog(@"%@", [NSThread currentThread]);
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:解决NSTimer循环引用、实现高效计时器

          本文链接:https://www.haomeiwen.com/subject/mtaoxltx.html