原文出处: Alexander Orlov   译文出处:南峰子(@南峰子_老驴)

转载自:ios.jobbole.com/83731/

我已经在iOS这个最好的移动平台上有几年的开发经验了。在这期间,我已以接触过很多的iOS应用和iOS工程师。

我们的世界很多好的开发者,但有时我发现他们中的一些人并不是很清楚如何充分利用这个最受欢迎的移动设备的整体潜力,来开发真正平滑的应用。

现在,我将尝试从我的视角,来说明一下为了让UITableView更快更平滑,工程师应该做哪些优化。


文章越往后,难度和深度也会不断增加,所以我将以一些你熟悉的东西来开始。文章后面将会讨论iOS绘画系统和UIKit更深层次的一些东西。

内建方法

我相信大多数阅读这篇文章的人都知道这些方法,但一些人,即便是使用过这些方法,也没有以正确的姿式来使用它们。

✻ ✻ ✻ ✻ ✻

首先是重用cell/header/footer的单个实例,即便是我们需要显示多个。这是优化UIScrollView(UITableView的父类)最明显的方式,UIScrollView是由苹果的工程师提供的。为了正确的使用它,你应该只有cell/header/footer类,一次性初始化它们,并返回给UITableView

在苹果的开发文档里面已经描述了重用cell的流程,在这就没有必须再重复了。

但重要的事情是:在UITableViewdataSource中实现的tableView:cellForRowAtIndexPath:方法,需要为每个cell调用一次,它应该快速执行。所以你需要尽可能快地返回重用cell实例。

不要在这里去执行数据绑定,因为目前在屏幕上还没有cell。为了执行数据绑定,可以在UITableViewdelegate方法tableView:willDisplayCell:forRowAtIndexPath:中进行。这个方法在显示cell之前会被调用。

✻ ✻ ✻ ✻ ✻

第二点也不难理解,但是有一件事需要解释一下。

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

正如我们所知,UITableViewUIScrollView的子类,而UIScrollView的作用是让用户可以与比屏幕实际尺寸更大的区域交互。任何UIScrollView的实例都使用诸如contentSizecontentOffset和其它许多属性来将正确的区域显示给用户。

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

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

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

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

如果我们没有一个cell的实例,那如何去计算它的高度呢?这里有一段示例代码,它使用类方法,并基于传入的宽度及显示的数据来计算高度值:

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

你在实现这一切的时候能获得了多少乐趣呢?大多数人会说没有。我没有保证过这事很容易。当然,我们可以构建我们自己的类来手动布局和计算高度,但有时候我们没有足够的时间来做这件事。你可以在Telegram的iOS应用代码中找到这种实现的例子。

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

尽管可以使用这些方法,但我强烈建议不要使用它们。另外,我也不建议使用复杂的数学计算来获取cell的高度,如果可能,只使用加、减、乘、除就可以。

但如果是AutoLayout呢?它真的跟我所说的一样慢么?你可能会很惊讶,但这是事实。如果你想让你的App在所有设备上都能平滑的滚动,你就会发现这种方法难以置信的慢。你使用的子视图越多,AutoLayout的效率越低。

AutoLayout相对低效的原因是隐藏在底层的命名为”Cassowary“的约束求解系统。如果布局中子视图越多,那么需要求解的约束也越多,进而返回cellUITableView所花的时间也越多。

哪一个更快呢:使用少量的值来执行基本的数学计算,还是找一个求解大量线性等式或不等式的系统么?现在想像一下,用户想要快速地滑动,每个cell的自动布局也执行着疯狂的计算。

✻ ✻ ✻ ✻ ✻

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

  • 重用cell实例:对于特殊类型的cell,你应该只有一个实例,而没有更多。
  • 不要在cellForRowAtIndexPath:方法中绑定数据,因为在此时cell还没有显示。可以使用UITableViewdelegate中的tableView:willDisplayCell:forRowAtIndexPath:方法。
  • 快速计算cell高度。对于工程师来说这是常规工作,但你将会为优化复杂cell的平滑滑动所付出的耐心而获取回报。

我们需要更深一步

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

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

✻ ✻ ✻ ✻ ✻

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

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

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

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

在iOS模拟器上运行App,在模拟器的菜单中选择’Debug‘,然后选中’Color Blended Layers‘。然后iOS模拟器就会将全部区域显示为两种颜色:绿色和红色。

绿色区域没有混合,但红色区域表示有混合操作。

正如你所看到的一样,在cell中至少有两处执行了混合操作,但你可能看不出差别来(这个混合操作是不必要的)。

每种情况都应该仔细研究,不同的情况需要使用不同的方法来避免混合。在我这里,我需要做的只是设置backgroundColor来实现非透明。

但有时候可能更复杂。看看这个:我们有一个渐变,但是没有混合。

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

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

如果能正确地利用了CPUGPU资源,它们将会均匀地负载,FPS保持在60帧。看起来就像下面这样:

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

大多数工程师在2010年夏季末时都面临这个问题,当时发布了iPhone 4。Apple发布了革命性的Retina显示屏和…非常普通的GPU。然而,通常情况下它仍然有足够的能力,但上面描述的问题却变得越来越频繁。

你可以在当前运行iOS 7系统的iPhone 4上看到这一现象—所有的应用都变得很慢,即使是最简单的应用。不过,应用这篇文章中的介绍的方法,即使是在这种情况下,你的应用也能达到60 FPS,尽管会有些困难。

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

我们可以在UIViewdrawRect:方法中使用CoreGraphics操作来执行CPU渲染,如下所示:

这段代码nice么?我会告诉你并非如此。甚至通过这种方式,你会撤销在一些UIView上(在任何情况下,它们都是不必要的)的所有缓存优化操作。但是,这种方法禁用了一些混合操作,卸载GPU,从而使UITableView的更顺畅。

但是记住:这提高了渲染性能,不是因为CPUGPU更快!它可以让我们通过为让CPU来执行某些渲染任务,从而卸载GPU,因为在很多情况下,CPU可能不是100%负载的。

优化混合操作的关键点是在平衡CPUGPU的负载。

✻ ✻ ✻ ✻ ✻

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

  • 减少iOS执行无用混合的区域:不要使用透明背景,使用iOS模拟器或者Instruments来确认这一点;如果可以,尽量使用没有混合的渐变。
  • 优化代码,以平衡CPUGPU的负载。你需要清楚地知道哪部分渲染需要使用GPU,哪部分可以使用CPU,以此保持平衡。
  • 为特殊的cell类型编写特殊的代码。

像素获取

你知道像素看起来是什么样的么?我的意思是,屏幕上的物理像素是什么样的?我肯定你知道,但我还是想让你看一下:

不同的屏幕有不同的制作工艺,但有一件事是一样的。事实上,每个物理像素由三个颜色的子像素组成:红、绿、蓝。

基于这一事实,像素不是原子单位,虽然对于应用来说它是。或者仍然不是?

直到带有Retina屏的iPhone 4发布前,物理像素都可以用整型点坐标来描述。自从有了Retina屏后,在Cocoa Touch环境下,我们就可以用屏幕点来取代像素了,同时屏幕点可以是浮点值。

在完美的世界中(我们尝试构建的),屏幕点总是被处理成物理像素的整型坐标。但在现实生活中它可能是浮点值,例如,线段可能起始于x0.25的地方。这时候,iOS将执行子像素渲染。

这一技术在应用于特定类型的内容(如文本)时很有意义。但当我们绘制平滑直线时则没有必要。

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

✻ ✻ ✻ ✻ ✻

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

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

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

那如何在代码中找到对应的位置呢?我总是使用手动布局,并且部分会自定义绘制,所以通常找到这些地方没有任何问题。如果你使用Interface Builder,那我对此深表同情。

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

✻ ✻ ✻ ✻ ✻

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

  • 对所有像素相关的数据做四舍五入处理,包括点坐标,UIView的高度和宽度。
  • 跟踪你的图像资源:图片必须是像素完美的,否则在Retina屏幕上渲染时,它会做不必要的抗锯齿处理。
  • 定期复查你的代码,因为这种情况可以会经常出现。

异步UI

可能这看起来有点奇怪,但这是一种非常有效的方法。如果你知道如何做,那么可以让UITableView滑动得更平滑。

现在我们来讨论一下你应该做什么,然后再讨论下你是否可能这么做。

✻ ✻ ✻ ✻ ✻

每个中等以上规模的应用都可能会使用带有媒体内容的cell:文本、图片、动画,甚至还有视频。

而所有这些都可能带有装饰元素:圆角头像、还’#‘号的文本、用户名等。

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

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

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

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

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

记住:需要尽快返回cell

✻ ✻ ✻ ✻ ✻

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

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

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

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

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

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

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

✻ ✻ ✻ ✻ ✻

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

  • 找到让你的cell无法快速返回的瓶颈。
  • 将操作移到后台线程,并在主线程刷新显示的内容。
  • 最后一招是设置你的CALayer为异步显示模式(即使只是简单的文本或图片)—这将帮你提高FPS。

结论

我尝试解释了iOS绘图系统(没有使用OpenGL,因为它的情况更少)的主要思路。当然有些看起来很模糊,但事实上这只是一些方向,你应该朝着这些方向来检查你的代码以找出影响滚动性能的所有问题。

具体情况具体分析,但原则是不变的。

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

做哪些优化,才能让 UITabelView 更顺滑?(转)的更多相关文章

  1. 如何做seo优化才能获取搜索引擎排名?

    现在网络上有很多网站,但是排名和流量都不理想,所以很多企业会很苦恼.所以我们经常思考如何使网站被搜索引擎喜欢,被用户喜欢,有一个良好的排名和流量? 在这个鱼龙混杂的网络中,seo优化实际上是seo网站 ...

  2. gcc -O0 -O1 -O2 -O3 四级优化选项及每级分别做什么优化【转】

    转自:http://blog.csdn.net/qinrenzhi/article/details/78334677 相关博客http://blog.chinaunix.net/uid-2495495 ...

  3. 一步步做程序优化-讲一个用于OpenACC优化的程序(转载)

    一步步做程序优化[1]讲一个用于OpenACC优化的程序 分析下A,B,C为三个矩阵,A为m*n维,B为n*k维,C为m*k维,用A和B来计算C,计算方法是:C = alpha*A*B + beta* ...

  4. 从数据库、代码和服务器对PHP网站Mysql做性能优化

    数据库优化是PHP面试几乎都会被问到的事情,也是我们工作中应该注意的事情,当然,如果是小网站无所谓优化不优化,网站访问量大了自然会暴漏数据库的瓶颈,这个瓶颈是各方面问题综合导致的,下面我们来做下数据库 ...

  5. 新网站如何做SEO优化【转】

    “百度快照变慢了.百度收录问题.关键词掉了”,这是在卢松松留言本被经常问及的问题,新手站长往往会因此吃不下饭.睡不着觉,网站的推广是一个漫长的过程,“心急吃不了热豆腐”,不要整天想着一建站就有巨大的流 ...

  6. 安装Win8后必做的优化

    原版或者精简版的希望都看看安装好Win8后必做的优化:1.关闭家庭组,因为这功能会导致硬盘和CPU处于高负荷状态关闭方法:Win+C – 设置 – 更改电脑设置 – 家庭组 – 离开如果用不到家庭组可 ...

  7. 20190726_安装CentOS7minimal版本后需要做的优化和配置

    20190726_安装CentOS7minimal版本后需要做的优化和配置 CentOS系统镜像下载地址:https://www.centos.org/ CentOS的Minimal(最小化安装版本) ...

  8. 82.使用vue后怎么针对搜索引擎做SEO优化?

    什么是SEO 搜索引擎优化(Search engine optimization,简称SEO),指为了提升网页在搜索引擎自然搜索结果中(非商业性推广结果)的收录数量以及排序位置而做的优化行为,是为了从 ...

  9. 干净win7要做几步才能运行第一个Spring MVC 写的动态web程序

    干净win7要做几步才能运行第一个Spring MVC 写的动态web程序: 1. 下载安装jdk 2. 配置Java环境变量 3. 测试一下第1,2两步是否完全成功:http://jingyan.b ...

随机推荐

  1. Android之监听手机软键盘弹起与关闭

    背景: 在很多App开发过程中需要在Activity中监听Android设备的软键盘弹起与关闭,但是Android似乎没有提供相关的的监听API给我们来调用,本文提供了一个可行的办法来监听软键盘的弹起 ...

  2. js的client、scroll、offset详解与兼容性

    clientWidth:可视区宽说明:样式宽+padding参考:js的client详解 scrollTop : 滚动条滚动距离说明:chrome下他会以为滚动条是文档元素的,所以需要做兼容:var ...

  3. 一类有依赖的树形背包dp方法

    失踪人口回归系列 这个标题是不是看起来很厉害呢233 给一道例题:有一个树,每一个节点代表一个物品,每个物品有重量和价值,每个物品必须先选父亲才能选自己.求给定重量内最大价值. 这题的思路十分的厉害. ...

  4. C# winform生成天气预报(转)

    原文地址 http://www.cnblogs.com/ChowYy/p/3382216.html?utm_source=tuicool&utm_medium=referral 项目需要,然后 ...

  5. BZOJ 3295: [Cqoi2011]动态逆序对

    3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 3865  Solved: 1298[Submit][Sta ...

  6. BZOJ 2957 楼房重建

    Description 小A的楼房外有一大片施工工地,工地上有N栋待建的楼房.每天,这片工地上的房子拆了又建.建了又拆.他经常无聊地看着窗外发呆,数自己能够看到多少栋房子. 为了简化问题,我们考虑这些 ...

  7. struts2: config-browser-plugin 与 convention-plugin 学习

    struts2被很多新手诟病的一个地方在于“配置过于复杂”,相信不少初学者因为这个直接改投Spring-MVC了.convention-plugin. config-browser-plugin这二个 ...

  8. 流形学习之等距特征映射(Isomap)

    感觉是有很久没有回到博客园,发现自己辛苦写的博客都被别人不加转载的复制粘贴过去真的心塞,不过乐观如我,说明做了一点点东西,不至于太蠢,能帮人最好.回校做毕设,专心研究多流形学习方法,生出了考研的决心. ...

  9. linux的一些常用命令

    这几天正好在研究linux系统,打算将下一个项目部署在linux系统的服务器上已提高安全性(被window 2003已经折磨的不行了),经过各方了解和深思熟虑后决定使用linux系统的CentOs版本 ...

  10. Bootstrap系列 -- 41. 带表单的导航条

    有的导航条中会带有搜索表单,在Bootstrap框架中提供了一个“navbar-form”,使用方法很简单,在navbar容器中放置一个带有navbar-form类名的表单.navbar-left”让 ...