解决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