iOS弹幕库OCBarrage-如何hold住每秒5000条巨量弹幕

最近公司做新需求, 原来用的老弹幕库, 已经无法满足需要. 迫不得已自己写了一套弹幕库OCBarrage. 这套弹幕库轻量, 可拓展, 高度自定义, 超高性能, 简单易上手.
无论哪家公司软件的性能绝对是衡量APP好坏的重要指标. 之前有一次开会, 我们领导说:"我们写的东西, 有哪些是可以拿的出手,让我们引以为豪的?". 之前还真就得想一会儿, 现在可以毫不犹豫的说我们的弹幕库绝对是一个好家伙.
做直播类软件核心功能一个是播放器另一个就是弹幕了. 现在iOS开源的弹幕库中性能好的不多, 弹幕量稍微大一点, 或者弹幕稍微复杂一点, 就会出现卡顿,这与它们的底层实现, 设计策略以及你的使用方法都有关系. 关键是动画单一,无法定制,满足不了动画的多样化需求!OCBarrage正是为解决这些问题而生的!
OCBarrage底层使用Core Animation驱动, Core Graphics绘图, GPU渲染, 性能极高, 哪怕是同时渲染5000条弹幕也不会感觉到卡顿. 开源地址:https://github.com/w1531724247/OCBarrage
(以下测试基于iPhone7真机)

同时渲染2000条弹幕.gif
同时渲染3000条弹幕.gif
同时渲染4000条弹幕.gif
同时渲染5000条弹幕.gif

对于全民直播这样的平台来说,在大主播高峰时期的弹幕量是很大的,特别是当主播说一句:“我们现在开始弹幕抽奖”。弹幕量瞬间就会涨的很高!所以对弹幕这一块的要求还是蛮高的.

性能优化原理

弹幕渲染时比较耗性能的点:

  1. 弹幕阴影

主播在户外直播时偶尔会有白色的背景, 而弹幕文字的颜色也是白色的, 这个时候弹幕飘到直播画面的白色区域会导致看不到文字内容. 为了解决这个问题我们通常会给弹幕文字添加一个隐影.以防止这种情况的发生. 然而别小看这几个像素阴影, 它可是性能消耗的大户. 哪怕是用GPU渲染因为是动态的实时的所以也相当吃性能. 在实验的过程中发现如果有文字阴影几十条弹幕就会出现弹幕卡顿, 结果就是弹幕抖动一跳一跳的.
解决办法就是用NSAttributeStringNSStrokeColorAttributeName属性设置文字的轮廓颜色替换文字阴影.效果对比如下:

text_shadow.png

text_stroke.png

都能解决我们的问题, 但是性能差的可不是一丁半点.

  1. 用CALayer替代UIView展示

与UIView相比CALayer更轻量. 性能更好.系统提供的组件为了保证其通用性, 难免有些冗余.这就是我们优化的空间.

  1. 弹幕文字下面的渐变色背景

彩色弹幕下面的渐变色背景如果用CAGradientLayer实现也是比较耗性能的, 但是如果是用图片呈现的话效果就会好的多, 但是不够灵活, 没关系, 我们都一并解决了.

  1. 将内容合成一张图片展现

将所有的内容呈现在layer上并布局好位置以后将所有的内容合成一张图片展现在barrageCell的layer上, 并删除所有的子subview及sublayer, 以提高性能.

效果演示

demonstration.gif
walkBarrage.gif
mixedImageAndText.gif
stopover.gif

使用用法

  • 第一步:

为新的弹幕类型新建一个数据模型 例如:OCBarrageWalkBannerDescriptor. 这个类必须继承自OCBarrageDescriptor类.

OCBarrageWalkBannerDescriptor.png

这样就创建新的弹幕类型的数据模型类, 我们可以在这个类里面添加新的弹幕属性例如:bannerLeftImageSrc, bannerMiddleColor, bannerRightImageSrc等等.

  • 第二步:

为新的弹幕类型创建建一个数据展示视图例如:OCBarrageWalkBannerCell. 这个新的弹幕类型的展示视图必须继承自OCBarrageTextCell类.

OCBarrageWalkBannerCell.png

在这个新的展示视图里我们可以添加展示相应数据的子视图,例如:leftImageView, middleImageView, rightImageView.
并为这个新的视图类添加一个相应的数据模型类的属性OCBarrageWalkBannerDescriptor *walkBannerDescriptor来传递数据.

  • 第三步:
    重写新视图OCBarrageWalkBannerCell- (void)setBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor方法. 并只能在这个方法里为walkBannerDescriptor属性赋值, 在这个方法里必须要调用[super setBarrageDescriptor:barrageDescriptor]方法, 不然barrageDescriptor属性将没有值, 并且部分属性设置将不生效.OCBarrageCell本身有一个barrageDescriptor属性引用数据模型. 但是为了方便拓展我们选择在第二步里为OCBarrageWalkBannerCell添加一个新的数据属性walkBannerDescriptor. 实质上OCBarrageWalkBannerCellbarrageDescriptor属性和walkBannerDescriptor指向的是同一个walkBannerDescriptor对象.
setBarrageDescriptor.png
  • 第四步:

重写新视图OCBarrageWalkBannerCell- (void)updateSubviewsData方法. 渲染引擎在渲染弹幕视图之前会自动调用这个方法. 我们可以在这个方法里为子视图设置数据

updateSubviewsData.png

.

  • 第五步:

在第四步设置好子视图的数据之后就可以计算并设置子视图的大小和位置.重写- (void)layoutContentSubviews方法, 并在这个方法里布局子视图的位置.渲染引擎会在调用- (void)updateSubviewsData方法之后自动调用- (void)layoutContentSubviews方法, 这个方法必须在主线程执行.

layoutContentSubviews.png
  • 第六步:

在布局好子视图的位置之后, 如果想要提高性能可以调用- (void)convertContentToImage方法, 将可以图像化的视图合成一张图片展示在cell的layer上, 渲染引擎会在调用- (void)layoutContentViews方法之后自动调用- (void)convertContentToImage方法, 这个方法必须在主线程执行.

convertContentToImage.png

如果不想将子视图的内容转化成图片只需重写- (void)convertContentToImage并留空即可:

convertContentToImage.png
  • 第七步:

如果想要进一步优化内存和性能, 可以重写- (void)removeSubViewsAndSublayers方法, 删除之前添加的的subView和sublayer, 并将子视图置为nil.

removeSubViewsAndSublayers.png

如果既想提高性能, 又有一些无法图片化的内容(例如:gif)需要展示, 可以重写- (void)removeSubViewsAndSublayers方法但不调用[super removeSubViewsAndSublayers]方法, 并选择性的删除一些子视图, 保留一些子视图.

如果不想删除子视图, 只需重写- (void)removeSubViewsAndSublayers方法并留空即可:

removeSubViewsAndSublayers.png

当然写到这里依然还有优化的空间, 后续会继续优化, 欢迎各位仁人志士共同探讨指点.
开源地址:https://github.com/w1531724247/OCBarrage

补充说明:
cell会在动画执行完之后调用- (void)prepareForReuse, 在数据设置并布局完准备展示的时候调用- (void)removeSubViewsAndSublayers, 如果在- (void)removeSubViewsAndSublayers里将子视图删除了, 下次重用的时候要在- (void)prepareForReuse里重新加一下子视图

想知道实际效果如何吗?赶快扫码下载体验吧!


图片.png

推荐阅读更多精彩内容