前段时间对我现在维护的项目,图文混排的效率比较低下,在4S等低端机型上,表现比较差。
经过跟踪和排查,发现当先使用的图文混排的机制,有些问题,会导致占用大量内存和多次重绘等比较严重的问题。

首先是试用库选型,我在Github上找了好久,发现了M80AttributedLabel,之后又发现了他写的这篇文章。经过几番抉择后,确定了,就是他。
因为这个库,对我们来说是迁移成本最低的。同时,代码简单又整洁,代码量也不是太大。如果后期有些自定义的需求,也可以很方便的修改和调整(当然这个库本身就有很多自定义的选项可以供我们使用)。

然后是确定重构范围,我的预想是只更换涉及图文混排的相关代码,对于我们现在时间紧,任务重的状态来说是最恰当的。但是了解了相关的代码之后,发现情况并不如我之前想的乐观。因为历史原因,构建Cell的所有代码都在- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath这个方法中。该方法有近1000行,非常不利于后期的维护和新进员工熟悉代码。了解到这个情况后,我决定当务之急是拆分这个方法。把每一类Cell有共性的部分抽象出来,作为基类。在基类上,根据不同类型的Cell,添加不同的内容。
现有项目共有一下几类消息,文字,图片,语音,找家教,分享图文链接和开放平台文章,其中,除开放平台文章不需要头像和姓名之外,其他类型消息,均需要头像和姓名;另外作为所以数据展示的Cell,相关的数据类型的引用也是有必要的。
因此现在就有一个基类,用来完成绘制和调整消息所涉及的名字,头像,和做相应的数据引用。另外我们需要5个不同的Cell子类,来完成相应的功能。因为消息背景的气泡需要我们根据当前内容,进行调整,所以把气泡也放在了子类里来实现。

确定好实现方式后,就按照方案来执行。经过测试,发现当前方案的效率确实有了提高,内存申请也有了相应的下降,但是仍然不是很理想。

其实在M80AttributedLabel的DEMO里,有告诉我们怎样来做这部分的优化。我们需要一个Cache来保证我们相应的文字Label只绘制一次。如果我们需要再次使用时,从Cache里取出来即可。不光文字的Label可以做缓存,他的高度也可以做缓存。这样就可以大幅度减少CPU的运算工作,但是可能需要多申请一点内存,因为我们使用的NSCache,他在内存紧张时,会自动里面的缓存的数据,所以内存这方面我们也不用担心。

加上了缓存方案之后,不仅CPU的占用变少了,而且内存的占用也变少了。这点令我很疑惑,不知道原因在哪里。初步判断应该是文字Label的绘制会占用大量内存,这些内存在绘制完成后,可能过不会立即释放,但是因为我们只绘制了一次,所以相应的内存申请也变少了。

另外发现一个很严重的问题是,我在消息界面申请的内存,在消息页面被销毁的时候,不会被释放。仔细跟踪后发现,是项目中使用的一个下拉刷新的控件,在声明代理的时候,内存策略选择的是strong,所以tableview的始终有引用在,不能被释放,但是因为Tableview有所有的Cell,和Cell的subivew的引用,所以这些内存也没有被释放掉。把内存策略改为weak,就解决了上述问题。