首先我个人认为设计合理、逻辑严谨的代码是不需要强行冷却的,但是我们不能保证我们面对的代码永远都是完美的,所以我在 AXKit 中就提供了这个冷却机制以延长那些癌症晚期的代码的寿命。
- 优点:执行代码像放技能一样,可以强行打破死循环、避免死循环、避免过高频率访问某一资源。
- 缺点:治标不治本,最好的解决办法是找出会产生问题的代码进行重构,从根源上解决问题。
应用场景
场景1
某种耗时耗能的操作,希望在某种条件下触发,但又担心用户频繁触发,例如进入某个页面的时候同步一下设备电量、或者同步一下运动数据;进入某个页面预加载一下子页面的网络数据……就可以这样:
- (void)reloadDataAndRefreshTableView{ ax_dispatch_cooldown(0, 2, @"reload data and refresh table view", dispatch_get_main_queue(), ^{ [self.dataList removeAllObjects]; [self reloadTableView]; }, ^{ AXLogFailure(@"操作过于频繁"); }); }
|
- (IBAction)btn1:(UIButton *)sender { ax_dispatch_cooldown(0, 60, self, dispatch_get_main_queue(), ^{ AXLogSuccess(@"技能施放成功!"); }, ^{ AXLogFailure(@"抱歉,技能在冷却中"); }); }
- (IBAction)btn2:(UIButton *)sender { ax_dispatch_cooldown(2, 120, self, dispatch_get_global_queue(0, 0), ^{ AXLogSuccess(@"技能施放成功!"); }, ^{ AXLogFailure(@"抱歉,技能在冷却中"); }); }
|
场景2
如何强行打破死循环?说实话我是没有遇到这种需求,仅仅是这个机制有这种能力,觉得挺有趣,就尝试一下:
- (void)loop{ AXLogFunc(); [self loop]; }
|
- (void)loop{ AXLogFunc(); ax_dispatch_cooldown(0, 0.0001, @"loop", dispatch_get_main_queue(), ^{ [self loop]; }, nil); }
|
实现原理
简单地说,就是给每一个代码块分配一个 dispatch_after
的函数,执行的时候开始计时,并且函数标记为 disable
,计时结束后重新 enable
。
inline ax_dispatch_operation_t ax_dispatch_cooldown(NSTimeInterval delay, NSTimeInterval cooldown,id token, dispatch_queue_t queue, void (^block)(void), void (^ __nullable block_cooling)(void)){ if (!token) { token = @"default token"; } if (!queue) { queue = dispatch_get_main_queue(); } BOOL cooling = is_cooling(token); if (cooling) { if (block_cooling) { block_cooling(); } } else { set_is_cooling(YES, token); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(cooldown * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ set_is_cooling(NO, token); }); return ax_dispatch_cancellable(delay, queue, block); } return nil; }
|
其中,对于代码是否正在冷却的判断利于了runtime机制,相当于新增了一个属性,用来保存是否正在冷却的状态:
static const void *AXBlockWrapperKey = &AXBlockWrapperKey;
static inline BOOL is_cooling(id token){ NSNumber *cooling = objc_getAssociatedObject(token, AXBlockWrapperKey); return cooling.boolValue; }
static inline void set_is_cooling(BOOL cooling, id token){ if (!token) { token = @""; } objc_setAssociatedObject(token, AXBlockWrapperKey, @(cooling), OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
|