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

2021-02-19  本文已影响0人  晨阳Xia

解决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
上一篇下一篇

猜你喜欢

热点阅读