@全局秒定时器
全局秒定时器
在我们的项目实践中,往往会用到周期为1秒的定时器。此外,我们用到的定时器周期,一般都是整数秒的。在复杂项目中,同一时刻这种定时器可能会开启很多个。这就导致了系统额外的开销。我们在想,对于这种整数秒周期的定时器,我们是否可以合并一下?这样既可以简化代码逻辑,又可以减少性能消耗。
于是我编写了一个全局定时器,用于周期是整数秒、精度要求不是很高的定时任务场合。
思路:
首先我们建立一个单例,我们称他为SecondTrigger,意为秒触发器。单例里面保存了一个NSTimer定时器。然后我们再建立一个可变数组,用于保存想要接受定时回调的target。当可变数组数量不为0时,定时器开启。可变数组内的target可以通过接口增加或减少。当定时器发现可变数组数量为0时,关闭定时器。
接口:
- (BOOL)addDelegate:(id<KKSecondTriggerDelegate>)delegate;
用于增加需要接受时间回调的target,增加后,target每一秒会接收到回调,target需要实现KKSecondTriggerDelegate协议,回调函数的原型就是在KKSecondTriggerDelegate总声明的
回调原形
@protocol KKSecondTriggerDelegate
@optional
- (void)secondTrigger:(KKSecondTrigger *)secondTrigger
counter:(NSInteger)counter;
@end
回调函数的原形就是在KKSecondTriggerDelegate里声明的。参数里除了秒触发器指针外,还有counter参数,表示这是第几次回调。当我们的定时器周期需要n秒出发一次的时候,我们只需要在回调函数里将 counter模上n,结果不为0就return即可。此外,counter可能还有其他一些用途。
扩展:
为了增加实用性,我们还增设了以下接口,用于参数的带入。
- (void)secondTrigger:(KKSecondTrigger *)secondTrigger
counter:(NSInteger)counter
parameter1:(id __nullable)parameter1
parameter2:(id __nullable)parameter2;
- (void)secondTrigger:(KKSecondTrigger *)secondTrigger
counter:(NSInteger)counter
parameter1:(id __nullable)parameter1;
代理协议里面增加了以下接口,用于参数的带出:
- (void)secondTrigger:(KKSecondTrigger *)secondTrigger
counter:(NSInteger)counter
parameter1:(id __nullable)parameter1
parameter2:(id __nullable)parameter2;
- (void)secondTrigger:(KKSecondTrigger *)secondTrigger
counter:(NSInteger)counter
parameter1:(id __nullable)parameter1;
以上接口只有参数个数不同,其他方面功能一样。便于用户在定时器里带入带出一些参数。如果调用的接口是n个参数的,那么n个参数的回调会被调用。n可以等于0、1、2。
其他接口:
1、由于定时器在开启以后,不会立刻触发,而是要等打破下一个周期才会触发。为了满处用户添加额外的定时器触发,我们还增开了fire接口,用于手动、额外的定时器触发
2、当不需要定时器触发的时候,我们可以调用removeDelegate来取消回调。
优点:
1、对于用途很广的秒周期的定时器,全局最多开启一个定时器,节约了资源
2、如果用户忘记调用removeDelegate,定时器会在target为nil的时候,自动关闭定时器。注意:定时器对target是若易用的
3、已经考虑了用户拖拽scrollView1是导致定时器不触发的情况
最终代码:
头文件:
#import <Foundation/Foundation.h>
@class KKSecondTrigger;
NS_ASSUME_NONNULL_BEGIN
@protocol KKSecondTriggerDelegate
@optional
- (void)secondTrigger:(KKSecondTrigger *)secondTrigger
counter:(NSInteger)counter
parameter1:(id __nullable)parameter1
parameter2:(id __nullable)parameter2;
- (void)secondTrigger:(KKSecondTrigger *)secondTrigger
counter:(NSInteger)counter
parameter1:(id __nullable)parameter1;
- (void)secondTrigger:(KKSecondTrigger *)secondTrigger
counter:(NSInteger)counter;
@end
@interface KKSecondTrigger : NSObject
+ (instancetype)sharedTrigger;
- (BOOL)addDelegate:(id<KKSecondTriggerDelegate>)delegate
parameter1:(id __nullable)parameter1
parameter2:(id __nullable)parameter2;
- (BOOL)addDelegate:(id<KKSecondTriggerDelegate>)delegate
parameter1:(id __nullable)parameter1;
- (BOOL)addDelegate:(id<KKSecondTriggerDelegate>)delegate;
- (BOOL)pause:(id<KKSecondTriggerDelegate>)delegate tag:(BOOL)tag;
- (void)resetSecondCount:(id<KKSecondTriggerDelegate>)delegate;
- (BOOL)removeDelegate:(id<KKSecondTriggerDelegate>)delegate;
// 默认是一秒触发一次,若需要手动触发,请调用这个方法
- (BOOL)triggerDelegate:(id<KKSecondTriggerDelegate>)delegate;
- (BOOL)isTriggering:(id<KKSecondTriggerDelegate>)delegate;
@end
NS_ASSUME_NONNULL_END
m文件:
#import "KKSecondTrigger.h"
@interface KKSecondTriggerElement : NSObject
@property (nonatomic, assign) NSInteger parameterCount;
@property (nonatomic, strong) id parameter1;
@property (nonatomic, strong) id parameter2;
@property (nonatomic, weak) id<KKSecondTriggerDelegate> delegate;
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, assign) BOOL paused;
@end
@implementation KKSecondTriggerElement
@end
@implementation KKSecondTrigger {
NSTimer* _theTimer;
// 用于存储KKSecondTriggerElement
NSMutableDictionary *_allElements;
}
+ (instancetype)sharedTrigger {
static id trigger = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
trigger = [[self alloc] init];
});
return trigger;
}
- (BOOL)addDelegate:(id<KKSecondTriggerDelegate>)delegate
parameter1:(id)parameter1
parameter2:(id)parameter2 {
[self verifyValidityWhenAddDelegate:delegate];
NSString* stringKey = [NSString stringWithFormat:@"%p", delegate];
KKSecondTriggerElement* element = [[KKSecondTriggerElement alloc] init];
element.parameterCount = 2;
element.delegate = delegate;
element.parameter1 = parameter1;
element.parameter2 = parameter2;
element.count = 0;
element.paused = NO;
_allElements[stringKey] = element;
return YES;
}
- (BOOL)addDelegate:(id<KKSecondTriggerDelegate>)delegate
parameter1:(id)parameter1 {
[self verifyValidityWhenAddDelegate:delegate];
NSString *stringKey = [NSString stringWithFormat:@"%p", delegate];
KKSecondTriggerElement *element = [[KKSecondTriggerElement alloc] init];
element.parameterCount = 1;
element.delegate = delegate;
element.parameter1 = parameter1;
element.parameter2 = nil;
element.count = 0;
element.paused = NO;
_allElements[stringKey] = element;
return YES;
}
- (BOOL)addDelegate:(id<KKSecondTriggerDelegate>)delegate {
[self verifyValidityWhenAddDelegate:delegate];
NSString* stringKey = [NSString stringWithFormat:@"%p", delegate];
KKSecondTriggerElement* element = [[KKSecondTriggerElement alloc] init];
element.parameterCount = 0;
element.delegate = delegate;
element.parameter1 = nil;
element.parameter2 = nil;
element.count = 0;
element.paused = NO;
_allElements[stringKey] = element;
return YES;
}
- (BOOL)triggerDelegate:(id<KKSecondTriggerDelegate>)delegate
{
KKSecondTriggerElement *aElement = [self getElementWithDelegate:delegate];
if (aElement.delegate)
{
return [self triggerElement:aElement];
}
return YES;
}
- (BOOL)isTriggering:(id<KKSecondTriggerDelegate>)delegate
{
KKSecondTriggerElement *aElement = [self getElementWithDelegate:delegate];
return aElement != nil;
}
- (BOOL)pause:(id<KKSecondTriggerDelegate>)delegate tag:(BOOL)tag
{
KKSecondTriggerElement *aElement = [self getElementWithDelegate:delegate];
if (aElement.delegate)
{
aElement.paused = tag;
}
return YES;
}
- (BOOL)removeDelegate:(id<KKSecondTriggerDelegate>)delegate
{
NSString *stringKey = [NSString stringWithFormat:@"%p", delegate];
if (_allElements && _allElements[stringKey])
{
[_allElements removeObjectForKey:stringKey];
return YES;
}
return NO;
}
- (void)resetSecondCount:(id<KKSecondTriggerDelegate>)delegate
{
KKSecondTriggerElement *aElement = [self getElementWithDelegate:delegate];
aElement.count = 0;
}
#pragma mark - private
- (void)onSecondTrigger
{
[_allElements.allKeys enumerateObjectsUsingBlock:^(NSString * _Nonnull keyPilot, NSUInteger idx, BOOL * _Nonnull stop) {
KKSecondTriggerElement *aElement = _allElements[keyPilot];
if (aElement.delegate) {
[self triggerElement:aElement];
} else {
[_allElements removeObjectForKey:keyPilot];
}
}];
if (_allElements.count == 0) {
_allElements = nil;
if (_theTimer) {
[_theTimer invalidate];
_theTimer = nil;
}
}
}
- (BOOL)triggerElement:(KKSecondTriggerElement*)element
{
if (element.paused) {
return NO;
}
element.count++;
id delegate = element.delegate;
if (element.parameterCount == 0) {
if ([delegate respondsToSelector:@selector(secondTrigger:counter:)]) {
[delegate secondTrigger:self counter:element.count];
return YES;
}
} else if (element.parameterCount == 1) {
if ([delegate respondsToSelector:@selector(secondTrigger:counter:parameter1:)]) {
[delegate secondTrigger:self counter:element.count parameter1:element.parameter1];
return YES;
}
} else if (element.parameterCount == 2) {
if ([delegate respondsToSelector:@selector(secondTrigger:counter:parameter1:parameter2:)]) {
[delegate secondTrigger:self counter:element.count parameter1:element.parameter1 parameter2:element.parameter2];
return YES;
}
}
return NO;
}
- (BOOL)verifyValidityWhenAddDelegate:(id<KKSecondTriggerDelegate>)delegate
{
if (delegate == nil) {
return NO;
}
if (_allElements == nil) {
_allElements = [[NSMutableDictionary alloc] init];
}
if ([self getElementWithDelegate:delegate].delegate) {
return NO;
}
if (_theTimer == nil)
{
_theTimer = [Tools startTimerWithInterval:1.0 target:self action:@selector(onSecondTrigger) userInfo:nil repeats:YES];
}
return YES;
}
- (KKSecondTriggerElement *)getElementWithDelegate:(id<KKSecondTriggerDelegate>)delegate
{
NSString *stringKey = [NSString stringWithFormat:@"%p", delegate];
return _allElements[stringKey];
}
@end
开启NSTimer的代码
@implementation Tools
+ (NSTimer*)startTimerWithInterval:(NSTimeInterval)ti
target:(id)aTarget
action:(SEL)action
userInfo:(id __nullable)userInfo
repeats:(BOOL)yesOrNo
{
NSString* strDebug = [NSString stringWithFormat:@"timeInterval = %f, target = %@, action = %@, userInfo = %@, repeats = %d", ti, aTarget, NSStringFromSelector(action), userInfo, yesOrNo];
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:ti target:[[KKTimer alloc]initWithTarget:aTarget andAction:action andDebugString:strDebug] selector:@selector(timerCallBack:) userInfo:userInfo repeats:yesOrNo];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
return timer;
}
@endif