NSMapTable实现一个定时器多处使用

某些时候,在一个页面我们可能需要多个定时器。今天就采用NSMapTable+单个GCD定时器去实现。先看下效果:
NSMapTable实现一个定时器多处使用_第1张图片
屏幕快照 2018-12-07 下午9.49.52.png

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

你可能感兴趣的:(NSMapTable实现一个定时器多处使用)