iOS 直播视频🙎🏿个人喜欢,收藏iOS开发常用知识点

仿映客刷礼物效果---代码优化

2016-10-18  本文已影响1184人  Rasping

上一篇文章《仿映客刷礼物效果---基本逻辑实现》中,分析了刷礼物效果的基本流程与具体实现代码。但还有一些BUG和一些可优化的地方没有处理,现在我们就来分析下这些遗留的问题。当然个人的能力是有限的,肯定还有很多我没有发现到的问题,如果大家在使用过程中有遇到其它的问题,欢迎大家及时指出,我好及时完善。

优化后的效果图如下:


效果图.png

废话不多说,先看问题。

问题一

问题描述

问题效果图如下图:


问题一.gif

从图中可以看到:当一个礼物动画组正在执行隐藏动画时,这时恰好收到一个新的与之相同类型的礼物消息,按正常逻辑来看,这个新的礼物消息应该应该作为一个新的动画组开始展示。但是,从GIF图中可以看到,连送按钮再次出现的时,新的动画组并没有开始展示,那新接收到礼物消息去哪里了呢?

问题原因

如果将隐藏动画的动画时间设置长一点,重复上面的问题流程。你就会发现,其实新接收到的礼物消息并不是消失了,而是被判定为一次连乘动画。所以这时其实是一边执行隐藏动画,一遍执行连乘动画,这就导致连乘动画很难被看到,从而造成了新接收到的礼物消息消失了。

问题解决

知道了问题原因,解决问题就非常简单了。这里我的解决方法是:让cell在执行隐藏动画时不被判定为正在执行动画,因此我给cell设置了几种动画状态,其逻辑关系如下图所示:


动画状态逻辑.png

有了动画状态之后,只用修改动画检测的判断条件就可以了,修改后的代码如下:

- (PresentViewCell *)examinePresentingCell:(id<PresentModelAble>)obj
{
    for (PresentViewCell *cell in self.showCells) {
        if ([cell.sender isEqualToString:[obj sender]] && [cell.giftName isEqualToString:[obj giftName]]) {
            //当前正在展示动画并且不是隐藏动画
            if (cell.state != AnimationStateNone && cell.state != AnimationStateHiding) return cell;
        }
    }
    return nil;
}

如果当前没有不为空闲并且也没有在隐藏动画,就判定当前cell可以执行连乘动画。
运行程序,再次测试,就会发现当cell正在执行隐藏动画时收到一条相同类型消息,新的消息会在新的动画组中展示,或是等有了空闲的cell时在展示。(修改后的具体效果可以在Demo中验证,下同)

问题二

问题描述

问题效果图如下:


问题二.gif

从图中可以看到:当连续多次快速的点击同一个发送按钮时,连乘的动画效果就消失了。

问题原因

很明显这里的问题原因就是因为:上一次点击的连乘动画还没执行完,就开始了下一次连乘动画,从而造成了这种效果。

问题解决

从问题原因中很容易想到这里需要用到缓存机制,即等到上一次动画执行完了再执行下一次动画。首先我们很容易想到的就是NSOperationQueue和dispatch_group_t,这是系统封装的两个任务队列,很容易实现上面的需求,而且还特别简单,只用在shakeAnimationWithNumber:里面实现缓存机制就行了。这里介绍下dispatch_group_t队列的实现方法,其代码如下:

- (void)shakeAnimationWithNumber:(NSInteger)number
{
    if (!_queue && !_group) {
        _queue = dispatch_queue_create("com.shakeCache.queue", DISPATCH_QUEUE_SERIAL);
        _group = dispatch_group_create();
        dispatch_group_notify(_group, dispatch_get_main_queue(), ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self hiddenAnimationOfShowShake:YES];
            });
        });
    }
    dispatch_group_async(_group, _queue, ^{
        [self startShakeAnimationWithNumber:number completion:nil];
    });
}

代码很简单,就是创建一个全局的串行队列,如果group任务完成就延时执行隐藏动画,每次调用都想group中添加一个连乘动画任务。
运行程序,你会发现问题还是存在,这是为什么呢?其实原因很简单,就是因为:UIView封装的动画是在子线程中执行,与添加任务操作不在同一个线程中。所以虽然任务是顺序添加的,但动画的执行并不是串行执行的。
这里的解决办法是:自己实现缓存--收到连乘动画先缓存,如果有缓存并且没有正在执行连乘动画,就取缓存,开始动画,动画完成就删除缓存,再去取缓存,只到没有缓存为止。具体实现代码如下:

- (void)shakeAnimationWithNumber:(NSInteger)number
{
    if (number > 0) [self.caches addObject:@(number)];
    if (self.caches.count > 0 && _state != AnimationStateShaking) {
        NSInteger cache        = [self.caches.firstObject integerValue];
        [self.caches removeObjectAtIndex:0];//不能删除对象,因为可能有相同的对象
        __weak typeof(self) ws = self;
        [self startShakeAnimationWithNumber:cache completion:^(BOOL finished) {
            [ws shakeAnimationWithNumber:-1];//传-1是为了缓存不被重复添加
        }];
    }
}

再次运行程序,就会发现连乘动画是串行执行了。

问题三

问题描述

在开始下一次动画去缓存时,这时刚好收到一个与取出的缓存相同类型的消息,又会去取缓存。在极端的情况下,这会造成两个相同类型的礼物动画同时展示。
因为这是一个逻辑上特别极端的情况,所以这里没有给出效果图。

问题原因

这个问题原因其实就是因为:取缓存到动画开始这段时间内收到一条与取出缓存相同类型的消息,导致这个新的消息在做动画检测时没有检测出来,所以新的消息可能也会作为新的动画组开始展示。

问题解决

这里的解决办法是将取缓存到动画开始这个时间缩减到最短,也就是在开始展示动画前就将cell的动画状态从AnimationStateNone设置成AnimationStateShowing。
关于这个问题,有兴趣的可以自行测试,测试时记得要增大取缓存到开始动画这段时间。具体操作就是:增大cell的显示动画时间,并且等cell的显示动画完成再将cell的动画状态从AnimationStateNone设置成AnimationStateShowing。(如果有测试出问题的,记得将问题效果的gif图分享给我!拜谢了!)

问题四

解决一个问题,就会带来新的问题。这里给动画做了这么多的缓存之后带来的新的问题是什么呢!想必测试过的应该已经发现了:连送按钮已经隐藏了,但是连乘动画还在执行,这肯定是不合逻辑的。

对于这个问题,只有在项目中有要求出现连送按钮才会出现,而且出现了也不一定算是问题(映客也没解决这个问题),所以这里就不解决这个问题了,如果确实有要求的,这里提供下思路:因为展示动画和连乘动画的动画时间都是确定的,所以很容易计算出执行完当前动画组需要的时间。然后每接收到一个连乘动画就对这个时间进行更新,并用代理或其它的形式将这个时间值传递出去,最后根据这个值对连送按钮进行控制就可以了。(当然了,有实现了这个思路或是有更好思路的,方便的话,也请分享给我,再次拜谢了!)

一个好的功能应该除了具有易用性之外,还应该具有良好的扩展性。

扩展

因为项目的需求不同,所以对展示动画与隐藏动画的要求也各部相同。这里就提供了对这连个动画自定义的接口,接口名如下:

/**
 *  自定义展示动画
 *
 *  @param flag 是否带有连乘动画
 */
- (void)customDisplayAnimationOfShowShakeAnimation:(BOOL)flag;
/**
 *  自定义隐藏动画
 *
 *  @param flag 是否带有连乘动画
 */
- (void)customHideAnimationOfShowShakeAnimation:(BOOL)flag;

如果想对这两动画进行简单的自定义,就可以在自定义cell中重写这两方法。需要注意的是,这两方法是在UIView封装的动画的animations回调中调用,所以只用修改cell的frame就够了。

一般项目中通过这种方法自定义cell的展示和隐藏动画就可以满足需求,如果项目中确实需要更绚丽的动画效果,就需要修改PrentViewCell的showAnimationWithModel:showShakeAnimation:prepare:completion:方法和hiddenAnimationOfShowShake:方法中的动画代码。

当然,还有其它的地方可以扩展,例如:让礼物消息只在指定的cell上展示,如VIP用户发送的礼物消息,在特定的位置上展示;还可以对消息的展示优先级进行区分,如不同等级的用户展示消息的优先级就不一样,等等这些都是有可能项目需要的需求。这里就不一一对这些需求进行实现了,关于优化后的Demo,大家可以点击这里下载。

最后,还是开篇那句话:个人的能力有限,肯定还有很多我没有发现到的问题,如果大家在使用过程中有遇到其它的问题,欢迎大家及时指出,我好及时完善。

上一篇下一篇

猜你喜欢

热点阅读