读后感系列之TableView滚动性能优化

本文为纯手打,内容取自南峰子老师的博客:http://southpeak.github.io/

里面有些地方没有实践,也不是很理解,大家共同学习。



内建方法:

首先是重用cell/header/footer的单个实例,几遍是我们需要显示多个.这是优化UIScrollerView最明显的方式,为了正确的使用它,你应该只有cell/header/footer类,一次性初始化他们,并返回给UITableView.

苹果开发文档里有重用cell的流程.

重要的事情是:在UITableView的dataSource中实现的tableView:cellForRowAtIndexPath:方法,需要为每个cell调用一次,他应该快速执行.所以需要尽可能快地返回重用cell实例.不要在这里进行数据绑定,因为目前在屏幕上还没有cell.为了执行数据绑定可以在UITableView的delegate方法tableView:willDisplayCell:forRowAtIndexPath:中进行.这个方法在显示之前会被调用.

这个方法对于cell定高的UITableView来说没有意义,但如果由于某些原因需要动态高度的cell的话,这个方法可以很容易地让滑动更流畅.

正如我们所知,UITableView是UIScrollerView的子类,而UIScrollerView的作用是让用户可以与比屏幕尺寸更大的区域交互.任何UIScrollerView的实例都是用诸如contentSizew、contentOffset和其他许多属性来将正确的区域显示给用户.

但是UITableView得问他在哪?正如所解释的一样,UITableView不会同时维护所有cell的实例。相反他只需要维护显示给用户的那些cell。

那么,UITableView是如何知道她的contentSize呢?他是通过计算所有cell的高度之和来计算contentSize的值。

UITableView的delegate方法tableView:heightForRowAtIndexPath:会为每个cell调用一次,所以你应该非常快地返回高度值。

很多人会犯一个错误,他们会在布局初始化cell实例并绑定数据后获取他们的高度。如果你想优化滑动的性能,就不应该以这种方式来计算cell的高度,因为这事难以置信的低效,iOS设备标准的60FPS将会降低到15-20FPS,滑动会变得很慢。

如果我们没有一个cell的实例,那如何计算他的高度呢?

它使用类方法,并基于传入的宽度及显示的数据来计算高度值

可以用以下方式来使用上面这个方法返回高度值给UITableView:

从iOS8 开始,我们可以在UITableView的delegate中使用自动高度计算,而不需要实现上面提到的方法。为了实现这一功能,你可能会使用AutoLayout,并将rowHeight变量设置为UITableViewAutomaticDimension。可以在StackOverflow中找到更多详细信息。

如果你想让你的App在所有设备上都能平滑的滚动,你会发现这种方法难以置信的慢,你使用的子视图越多,AutoLayout的效率越低,原因是隐藏在底层的命名为“Cassowary”的约束求解系统。如果布局中子视图越多,那么需要求解的约束也越多,进而返回cell给UITableView所花的时间也越多。

使用内建方法优化UITableView的正确方法是:

重用cell实例:对于特殊类型的cell,你应该只有一个实例,而没有更多。

不要在cellForRowAtIndexPath:方法中绑定数据,因为在此时cell还没有显示。可以使用UITableView的delegate中的tableView:willDisplayCell:forRowAtIndexPath:方法。

快速计算cell高度。


更深一步


上面提到的这些点不足以实现真正的平滑滚动,特别是当你需要实现一些复杂的cell(如有大量的简便、视图、交互元素、一些修饰元素等等)时,这变得尤其明显。

这种情况下,UITableView很容易变得缓慢,即便是做了上面所有的事情。UITableViewCell中的视图越多,滑动时FPS越低。但在使用了手动布局优化了高度计算后,问题就不在布局了,而在渲染了。

让我们把关注点放在UIView的opaque上属性上,文档中说它用于辅助绘图系统定义UIView是否透明,如果不透明,则绘图系统在渲染视图时可以做一些优化,以提高性能。

我们需要性能,或者不是?用户可能快速地滑动table,如使用scrollsToTop特性,但他们可能没有最新的iPhone,所以cell必须快速的被渲染。比通常的视图更快。

渲染最慢的操作之一是混合(blending)。混合操作由GPU来执行,因为这个硬件就是用来做混合操作的(当然不只是混合)。

你可能已经猜到提高性能的方法是减少混合操作的次数,在此之前,我们需要找到它。

在模拟器上运行APP,在模拟器的菜单中选择‘Debug’,然后选中‘Color Blended Layers’。然后模拟器就会将全部区域显示为两种颜色:绿色和红色。(绿色没有混合,红色区域表示有混合操作)每种情况都应该仔细研究,不同的情况需要使用不同的方法来避免混合。一般做法是设置backgroundColor来实现非透明。

但有时候可能更复杂。有一个渐变,但是没有混合。


如果想要使用CAGradientLayer来实现这个效果,你将会很失望:在iPhone6中FPS将会降到25-30,快色滑动变得不可能。

这确实发生了,因为我们混合了两个不同层的内容:UILabel的CATextLayer和我们的CAGradientLayer。

当设备需要执行很多混合操作时,问题就出现了:GPU是满载的,但CPU却保持低负载,而显得没有太大用处。

所以需要怎么做呢?事实上,解决的方案是:使用CPU来渲染!这将不会加载GPU,这样就无法执行混合操作,例如,在执行动画的CALayer上。

我们可以在UIView的drawRect:方法中使用CoreGraphics操作来执行CPU渲染,


通过这种方式,你会撤销在一些UIView上(在任何情况下,他们都是不必要的)的所有缓存优化操作。但是,这种方法禁用了一些混合操作,卸载GPU,从而使UITableView更顺畅。

但是记住:这提高了渲染性能,不是因为CPU比GPU更快,他可以让我们通过为让CPU来执行某些渲染任务,从而卸载GPU,因为在很多情况下,CPU可能不是100%负载的。优化混合操作的关键点是在平衡CPU和GPU的负载。(这个CoreGraphics好难理解感觉!)

优化UITableView中绘制数据操作的小结:

减少iOS执行无用混合的区域:不要使用透明背景,使用模拟器或者Instruments来确认这一点,如果可以尽量使用没有混合的渐变。

优化代码,以平衡CPU和GPU的负载。你需要清楚的知道那部分渲染需要使用GPU,那部分可以使用CPU,以此保持平衡。

为特殊的cell类型编写特殊的代码。


像素获取


自从有了Retina屏后,在Cocoa Touch环境下,我们就可以用屏幕点来取代像素了,同时屏幕点可以是浮点值。

现实生活中它可能是浮点值,例如,线段可能起始于x为0.25的地方。这时候,iOS将执行子像素渲染。这一技术在应用于特定类型的内容是很有意义。但当我们绘制平滑直线时则没有必要。

如果所有的平滑线段都使用子像素渲染技术来渲染,那会让你iOS执行一些不必要的任务,从而降低FPS。

什么情况下会出现这种不必要的子像素抗锯齿操作呢?最常发生的情况是通过代码计算而变成浮点值的视图坐标,或者是一些不正常的图片资源,这些图片的大小不是对起到屏幕的物理像素上的(例如,你有一张在Retina显示屏上的大小为60*61的图片,而不是60*60的)。

在前面我们讲到,要解决问题,首先需要找到问题在哪。在模拟器上运行程序,在“Debug”菜单中选中“Color Misaligned Image”。

这一次有两种高亮区域:品红色区域会执行子像素渲染,而黄色区域是图片大小没有对齐的情况。

通常,为了解决这个问题,你只要简单地使用ceilf,floorf和CGRectIntegral方法来对坐标做四舍五入处理。就是这样!

通过上面的讨论,我想建议你以下几点:

对所有像素相关的数据做四舍五入处理,包括点坐标,UIView的高度和宽度。

跟踪你的图像资源:图片必须是像素完美的,否则在Retina屏幕上渲染时,它会做不必要的抗锯齿处理。

定期复查你的代码,因为这种情况可能会经常出现。


异步UI

每个中等以上规模的应用都可能会使用带有媒体内容的cell:文本、图片、动画,甚至还有视频。而所有这些都可能带有装饰元素:圆角头像、带‘#’号的文本、用户名等。

我们已经多次提及尽可能快地返回cell的需求,而在这里有一些麻烦:clipsToBounds很慢,图片需要从网络加载,需要在字符串中定位#号,和许多其他的问题。

优化的目标是很明确的:如果在主线程中执行这些操作,则会让你不能很快地返回cell。

在后台加载图片,在相同的地方处理圆角,然后将处理后的图片制定给UIimageView。

立刻显示文本,但在后台定位#号,然后使用属性字符串来刷新显示。

在你的cell中,需要具体情况具体分析,但主要的思想是在后台执行大的操作。这可能不只是网络代码,你需要使用Instruments来找到他们。

记住:需要尽快返回cell

有时候,上面的所有技术可能都帮不上忙。如GPU仍然不能使用时(iPhone4+iOS7),cell中有很多内容时,需要CALayer的支持以实现动画时(在drawRect:中实现起来真的很麻烦)。

在这种情况下,我们需要在后台渲染所有其他东西。此外他能在用户快速滑动UITableView时有效提高FPS。

我们Facebook的应用。为了检测这些,你可能需要往下滑动足够的高度,然后点击状态栏。列表会往上滑动,因此你可以清楚地看到此时没有渲染cell,如果想要更精确,则不能及时获得。

这很简单,所以你可以自己试试,这时,你需要设置CALayer的drawsAsynchronously属性为YES。

但是我们可以检查这些行为的必要性,在模拟器上运行程序,然后选择“Debug”菜单中的“Color Offscreen-Rendered”。现在所有在后台渲染的区域都被高亮为黄色。


如果你为某些层开启了这一模式,但是它没有高亮显示,那么它就不够慢

为了在CALayer层找到瓶颈并进一步减少它,你可以使用Instruments里面的Time Profiler。

(关于Instruments使用工具有Leaks、Allocations,Time Profiler帮助我们分析代码的执行实现,找出导致程序变慢的原因,告诉我们‘实践都去哪了’)

这里异步优化UI的实现清单:

找到让你的cell无法快速返回的瓶颈。

将操作移到后台线程,并在主线程刷新显示的内容。

最后一招是设置你的CALayer为异步显示模式(即使只是简单的文本或图片)这将帮你提高FPS。



我尝试解释了iOS绘图系统(没有使用OpenGL,因为他的情况更少)的主要思路。

当然有些看起来很模糊,但事实上这只是一些方向,你应该朝着这些方向来检查你的代码以找出影响滚动性能的所有问题。具体情况具体分期,但原则是不变的。

获取完美平滑滚动的关键是非常特殊的代码,他能让你竭尽iOS的能力让你的应用更加平滑。完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容