1.NSMapTable
在iOS6之后,Objective-C Foundation框架中添加了两个类分别是NSHashTable和NSMapTable,在这里使用的是NSMapTable来代替字典。在初始化的时候,key用NSMapTableWeakMemory修饰,value使用NSPointerFunctionsCopyIn修饰,这样做的目的是对外界传入的target的计数器不受定时器的影响。
self.targetsMap = [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory) valueOptions:NSPointerFunctionsCopyIn];
2.GCD定时器
在这里使用GCD定时器,使用简单,而且不会造成循环引用。
核心代码如下:
- (void)scheduledDispatchTimerWithName:(NSString *)timerName
timeInterval:(double)interval
queue:(dispatch_queue_t)queue
repeats:(BOOL)repeats
action:(dispatch_block_t)action {
if (nil == timerName)
return;
if (nil == queue)
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = [self.timerContainer objectForKey:timerName];
if (!timer) {
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_resume(timer);
[self.timerContainer setObject:timer forKey:timerName];
}
/* timer精度为0.01秒 */
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.01 * NSEC_PER_SEC);
__weak typeof(self) weakSelf = self;
/* 移除之前的action */
[weakSelf removeActionCacheForTimer:timerName];
dispatch_source_set_event_handler(timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
action();
});
if (!repeats) {
[weakSelf cancelTimerWithName:timerName];
}
});
}
3.具体实现细节
首先,创建一个定时器的管理类ZFCGCDTimerManager,它是一个单例。提供三个方法,一个是添加target的方法(也就是添加定时器对象)、一个是移除单个target,还有一个是移除所有的target。
3.1 添加target
/**
添加一个定时器
@param target 要添加定时器的对象
@param handle 回调
*/
- (void)addTarget:(id)target handle:(ZFCTimerMangerHandle)handle{
if (!handle || !target) return;
SemaphoreBegin
[self.targetsMap setObject:handle forKey:target];
if (self.targetsMap.count > 0) {
[self startGCDTimer];
}
SemaphoreEnd
}
在这个添加的方法中,target就是添加定时器的对象,handle就是定时器的回调block。在这个方法里面,将target作为key,handle作为value,并添加到targetsMap中去。需要注意的是,在操作targetsMap的时候,做了线程安全的防护,也就是SemaphoreBegin和SemaphoreEnd,这是对GCD信号锁的宏定义,代码如下:
//加锁
#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
semaphore = dispatch_semaphore_create(1); \
}); \
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//解锁
#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);
然后,判断targetsMap中的count是否>0,如果大于0,就开启这个定时器
[[ZFCGCDTimerManager sharedInstance]scheduledDispatchTimerWithName:timer timeInterval:1.0 queue:nil repeats:YES action:^{
//防止枚举遍历的时候,添加/移除
NSMapTable *tempTargetsMap = self.targetsMap.copy;
for (id target in tempTargetsMap.keyEnumerator) {
ZFCTimerMangerHandle handle = [tempTargetsMap objectForKey:target];
handle(target,self);
}
}];
在开启定时的这个方法里面,通过遍历targetsMap,并取出器value->block,执行这些block,回调到每个target中去
3.2 移除单个target
- (void)removeTarget:(id)target {
if (!target) return;
SemaphoreBegin
[self.targetsMap removeObjectForKey:target];
if(self.targetsMap.count == 0){
[self stopGCDTimer];
}
SemaphoreEnd
}
在这里,主要是通过判断targetsMap的count=0,当最后一个target移除的时候,就关闭定时器。
[[ZFCGCDTimerManager sharedInstance] cancelTimerWithName:timer];
3.3 移除所有的target
SemaphoreBegin
[self.targetsMap removeAllObjects];
[self stopGCDTimer];
SemaphoreEnd
内容也比较简单,主要是利用了NSMapTable容器的弱引用的机制、GCD定时器和GCD信号锁。代码上传了github:https://github.com/zfc769956454/ZFCTimerManger