背景
在我们开发 App 的过程中, 或多或少都遇到过需要使用倒计时的场景, 大多数应用中的用户登录注册过程中获取验证码的倒计时, 电商或者外卖 App 中的订单送达的倒计时, 以及秒杀类 App 的秒杀倒计时等. 对于这些需要倒计时的场景, 通常情况下的解决方案是: 在需要展示倒计时的各 View 模块各自维护一个自己的倒计时 Timer, 通过 Timer 的回调和模块本身需要的倒计时时间来更新对应 View 的倒计时的显示, 再在此基础上加上对应的时间校准方案, 一个简单的倒计时需求就完成了.
问题
对于 App 内倒计时的业务如果只出现在单一的页面或者是少数的页面场景中没什么太大的问题的, 通常对于秒杀类 App 的倒计时场景往往是在某个页面或者某几个页面中有多个倒计时共同存在的, 这种产品需求的技术展现方式可能是 TableView 或者 CollectionView 中的多个 Cell, 也可能是多个自定义的 View 模块, 如果我们此时依然使用每个 Cell 或者每个 View 模块各自维护一个单独的倒计时 Timer, 当前 App 内就会同时存在多个定时器 Timer, 这对于性能来说是存在一定程度的影响的. 那么我们怎么才能更好的解决多倒计时场景的问题呢?
解决方案
既然我们不能让每一个显示倒计时的 View 模块各自维护一个定时器 Timer, 那我们就提供一个专门的模块 TimerService 来提供倒计时的服务, TimerService 内部负责维护唯一一个定时器, 同时提供添加和移除监听者的接口以及监听者需要实现的协议 protocol, 内部通过 HashTable 来存储监听者, 每次定时器回调, 遍历所有监听者进行回调, 监听者在不需要接收定时器回调的时候只需要从 TimerService 中移除即可.
TimerService.h 对外提供的 API 和监听者需要实现的协议主要如下:
- // 监听者需要实现的协议
- @protocol TimerListener <NSObject>
- @required
- - (void)didOnTimer:(TimerService *)timer;
- @end
- // 对接提供的主要接口
- + (instancetype)sharedInstance;
- - (void)addListener:(id<TimerListener>)listener;
- (void)removeListener:(id<TimerListener>)listener;
TimerService.m 的内部主要实现如下:
- // 定时器回调
- - (void)onTimer {
- [self.map.allObjects enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- id<TimerListener> listener = obj;
- if([listener respondsToSelector:@selector(didOnTimer:)]){
- [listener didOnTimer:self];
- }
- }];
- }
- #pragma mark - public
- - (void)addListener:(id<TimerListener>)listener {
- TIMER_SERVICE_LOCK(self.operationsLock)
- if(![self.map containsObject:listener]){
- [self.map addObject:listener];
- if(self.map.count> 0){
- // 启动
- [self startTimer];
- }
- }
- TIMER_SERVICE_UNLOCK(self.operationsLock)
- }
- - (void)removeListener:(id<TimerListener>)listener {
- TIMER_SERVICE_LOCK(self.operationsLock)
- if([self.map containsObject:listener]){
- [self.map removeObject:listener];
- if(self.map.count == 0){
- // 暂停
- [self stopTimer];
- }
- }
- TIMER_SERVICE_UNLOCK(self.operationsLock)
}
使用
需要接收定时器回调的模块, 只要实现 TimerListener 协议, 在需要接收定时器回调的时把其添加到 TimerService 中, 在业务不需要接收定时器回调的时候把其从 TimerService 中移除即可, 这样所有的倒计时业务只需要维护一个定时器即可搞定.
来源: https://juejin.im/post/5bf121b6f265da613a539d9c