<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>性能 on 土土哥的技术 Blog</title><link>https://tutuge.me/tags/%E6%80%A7%E8%83%BD/</link><description>Recent content in 性能 on 土土哥的技术 Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>tutuge</copyright><lastBuildDate>Thu, 19 Feb 2015 00:00:00 +0000</lastBuildDate><atom:link href="https://tutuge.me/tags/%E6%80%A7%E8%83%BD/index.xml" rel="self" type="application/rss+xml"/><item><title>提升UITableView性能-复杂页面的优化</title><link>https://tutuge.me/2015/02/19/%E6%8F%90%E5%8D%87UITableView%E6%80%A7%E8%83%BD-%E5%A4%8D%E6%9D%82%E9%A1%B5%E9%9D%A2%E7%9A%84%E4%BC%98%E5%8C%96/</link><pubDate>Wed, 18 Feb 2015 17:28:41 +0000</pubDate><guid>https://tutuge.me/2015/02/19/%E6%8F%90%E5%8D%87UITableView%E6%80%A7%E8%83%BD-%E5%A4%8D%E6%9D%82%E9%A1%B5%E9%9D%A2%E7%9A%84%E4%BC%98%E5%8C%96/</guid><description>&lt;img src="https://tutuge.me/assets/2015/02/19/%E6%8F%90%E5%8D%87UITableView%E6%80%A7%E8%83%BD-%E5%A4%8D%E6%9D%82%E9%A1%B5%E9%9D%A2%E7%9A%84%E4%BC%98%E5%8C%96/blog_ios_speed_up_uitableview_1_1.png" alt="Featured image of post 提升UITableView性能-复杂页面的优化" /&gt;&lt;h2 id="前言"&gt;&lt;a href="#%e5%89%8d%e8%a8%80" class="header-anchor"&gt;&lt;/a&gt;前言
&lt;/h2&gt;&lt;p&gt;随着App的用户界面的内容越来越丰富，再强的手机可能都无法同时渲染复杂的UI界面和保证流畅的体验。所以，我们这些程序猿=。=在写代码的时候就要注意，如何尽可能提高用户的操作流畅性。&lt;/p&gt;
&lt;p&gt;之前的做的项目，&lt;a class="link" href="https://itunes.apple.com/us/app/qing-ju-yin-le-quan-guo-zui/id913235479?l=zh&amp;amp;ls=1&amp;amp;mt=8" target="_blank" rel="noopener"
 &gt;青桔音乐iOS客户端&lt;/a&gt;里面的首页就是一个类似微信朋友圈的“动态”页面，大致如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="青桔动态页面" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://tutuge.me/assets/2015/02/19/提升UITableView性能-复杂页面的优化/blog_ios_speed_up_uitableview_1_1.png"&gt;&lt;/p&gt;
&lt;p&gt;如果是你，你会怎么实现这个页面呢？&lt;/p&gt;
&lt;p&gt;这还用问，当然是用UITableView+自定义的UITableViewCell。&lt;/p&gt;
&lt;p&gt;UITableView是可以滑动的，为了不让用户在滑动中感到有卡顿，该如何优化？下面，我就写一下我自己在做项目时的“经验”~&lt;/p&gt;
&lt;h2 id="优化"&gt;&lt;a href="#%e4%bc%98%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;优化
&lt;/h2&gt;&lt;p&gt;主要分为以下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只定义一种Cell。&lt;/li&gt;
&lt;li&gt;提前计算并缓存每个Cell的高度。&lt;/li&gt;
&lt;li&gt;提前创建真正显示的、需要加工的数据并缓存。&lt;/li&gt;
&lt;li&gt;缓存View！&lt;/li&gt;
&lt;li&gt;其它。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="只定义一种cell"&gt;&lt;a href="#%e5%8f%aa%e5%ae%9a%e4%b9%89%e4%b8%80%e7%a7%8dcell" class="header-anchor"&gt;&lt;/a&gt;只定义一种Cell
&lt;/h3&gt;&lt;p&gt;乍一看，这个界面至少有3种样式的Cell，为什么只定义一种呢？&lt;/p&gt;
&lt;h4 id="分析结构"&gt;&lt;a href="#%e5%88%86%e6%9e%90%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;分析结构
&lt;/h4&gt;&lt;p&gt;仔细分析一下，页面中每个Cell的内容都有头像、标题、正文、评论、其它（歌曲、图片、歌手）。所以，从整体上看，每个Cell的结构是一致的！&lt;/p&gt;
&lt;h4 id="重用大致固定数量的cell"&gt;&lt;a href="#%e9%87%8d%e7%94%a8%e5%a4%a7%e8%87%b4%e5%9b%ba%e5%ae%9a%e6%95%b0%e9%87%8f%e7%9a%84cell" class="header-anchor"&gt;&lt;/a&gt;重用=大致固定数量的Cell
&lt;/h4&gt;&lt;p&gt;并且，凡是认真研究过UITableView的人应该都知道，Apple已经为我们提供了Cell的重用，如用“&lt;strong&gt;registerNib:forCellReuseIdentifier&lt;/strong&gt;”方法注册自定义Cell的Nib，然后在“&lt;strong&gt;cellForRowAtIndexPath&lt;/strong&gt;”的时候用“&lt;strong&gt;dequeueReusableCellWithIdentifier&lt;/strong&gt;”获取可以重用的Cell，所以，无论UITableView要显示内容有多少，真正创建出的Cell可能只有5、6个。&lt;/p&gt;
&lt;p&gt;所以，我们完全可以只创建一种Cell，虽然这样一个Cell的“体积”可能会很大，但是介于Cell的数量不会很多，所以完全可以接受。&lt;/p&gt;
&lt;h5 id="只定义一种cell的好处"&gt;&lt;a href="#%e5%8f%aa%e5%ae%9a%e4%b9%89%e4%b8%80%e7%a7%8dcell%e7%9a%84%e5%a5%bd%e5%a4%84" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;只定义一种Cell的好处&lt;/strong&gt;
&lt;/h5&gt;&lt;ul&gt;
&lt;li&gt;减少代码量，减少Nib文件的数量，统一一个Nib文件定义Cell，容易修改、维护。&lt;/li&gt;
&lt;li&gt;基于Cell的重用，真正运行时铺满屏幕所需的Cell数量大致是固定的，设为&lt;strong&gt;N&lt;/strong&gt;个。所以如果如果只有一种Cell，那就是只有&lt;strong&gt;N&lt;/strong&gt;个Cell的实例；但是如果有&lt;strong&gt;M&lt;/strong&gt;种Cell，那么运行时最多可能会是“&lt;strong&gt;M x N = MN&lt;/strong&gt;”个Cell的实例，虽然可能并不会占用太多内存，但是能少点不是更好吗。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="善用hidden隐藏显示subview"&gt;&lt;a href="#%e5%96%84%e7%94%a8hidden%e9%9a%90%e8%97%8f%e6%98%be%e7%a4%basubview" class="header-anchor"&gt;&lt;/a&gt;善用hidden隐藏（显示）Subview
&lt;/h4&gt;&lt;p&gt;既然只定义一种Cell，那该如何显示不同类型的内容呢？
答案就是，把所有不同类型的view都定义好，放在cell里面，通过hidden显示、隐藏，来显示不同类型的内容。如下图定义Cell：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cell示意图" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://tutuge.me/assets/2015/02/19/提升UITableView性能-复杂页面的优化/blog_ios_speed_up_uitableview_1_2.jpg"&gt;&lt;/p&gt;
&lt;p&gt;图中的Subview1、Subview2、Subview3就是不同类型Cell的不同之处，所以我们在“&lt;strong&gt;cellForRowAtIndexPath&lt;/strong&gt;”函数中，设置Cell的样式、内容时，就可以通过显示、隐藏这三个子view来显示。&lt;/p&gt;
&lt;p&gt;毕竟，在用户快速滑动中，只是单纯的显示、隐藏subview比实时创建要快得多。&lt;/p&gt;
&lt;h3 id="提前计算并缓存每个cell的高度"&gt;&lt;a href="#%e6%8f%90%e5%89%8d%e8%ae%a1%e7%ae%97%e5%b9%b6%e7%bc%93%e5%ad%98%e6%af%8f%e4%b8%aacell%e7%9a%84%e9%ab%98%e5%ba%a6" class="header-anchor"&gt;&lt;/a&gt;提前计算并缓存每个Cell的高度
&lt;/h3&gt;&lt;p&gt;开发过Android，用过Android的ListView以后，对UITableView需要提前计算Cell的高度很不适应。=。=&lt;/p&gt;
&lt;p&gt;首先要确定的是，在iOS中，系统会先调用“&lt;strong&gt;tableView:heightForRowAtIndexPath:&lt;/strong&gt;”获取每个Cell即将显示的高度，从而确定整个UITableView的布局。然后才调用“&lt;strong&gt;tableView:cellForRowAtIndexPath&lt;/strong&gt;”获取每个Cell，我们也是在这里填充、设置Cell的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;所以，既然高度总会被用到，那就早早的在获取数据时就计算好吧！&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="在modelentity中计算并保存cell的高度"&gt;&lt;a href="#%e5%9c%a8modelentity%e4%b8%ad%e8%ae%a1%e7%ae%97%e5%b9%b6%e4%bf%9d%e5%ad%98cell%e7%9a%84%e9%ab%98%e5%ba%a6" class="header-anchor"&gt;&lt;/a&gt;在Model（Entity）中计算并保存Cell的高度
&lt;/h4&gt;&lt;p&gt;其实，在Model（Entity）中保存UI的参数是很奇怪的=。=（最好放在ViewModel中，就是MVVM模式的），我们的Entity可能就是下面的样子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@interface DataEntity : NSObject
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;//原始数据
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@property(copy, nonatomic) NSString *content;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@property(copy, nonatomic) NSString *title;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;//Cell 高度
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@property(assign, nonatomic) CGFloat cellHeight;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;//计算高度
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- (void)calculateCellHeight;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@end
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样，就不用在“&lt;strong&gt;tableView:heightForRowAtIndexPath:&lt;/strong&gt;”中每次都计算了。&lt;/p&gt;
&lt;h3 id="提前创建真正显示的需要加工的数据并缓存"&gt;&lt;a href="#%e6%8f%90%e5%89%8d%e5%88%9b%e5%bb%ba%e7%9c%9f%e6%ad%a3%e6%98%be%e7%a4%ba%e7%9a%84%e9%9c%80%e8%a6%81%e5%8a%a0%e5%b7%a5%e7%9a%84%e6%95%b0%e6%8d%ae%e5%b9%b6%e7%bc%93%e5%ad%98" class="header-anchor"&gt;&lt;/a&gt;提前创建真正显示的、需要加工的数据并缓存
&lt;/h3&gt;&lt;p&gt;Cell中显示的内容，很多时候可能并不是直接从服务器拿到的数据，而是经过“加工”的数据。如本文中的“动态”也，每个Cell的标题、正文都有可点击的连接Link、表情图片等富文本内容，而我们一般用NSAttributeString类来显示。&lt;/p&gt;
&lt;p&gt;既然每次都会用到，倒不如在获取到数据的时候就创建、加工好这些内容，等到需要现实的时候，直接拿来用不就行了。&lt;/p&gt;
&lt;p&gt;所以，我们的Entity类可能变成下面这个样子:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-objectivec" data-lang="objectivec"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@interface&lt;/span&gt; &lt;span class="nc"&gt;DataEntity&lt;/span&gt; : &lt;span class="nc"&gt;NSObject&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//原始数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nonatomic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nonatomic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//Cell 高度
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nonatomic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CGFloat&lt;/span&gt; &lt;span class="n"&gt;cellHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//真正显示的内容
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nonatomic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;NSAttributedString&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;showTitle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nonatomic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;NSAttributedString&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;showContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//计算高度
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;calculateCellHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//创建、加工真正显示的内容
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;setupShowTitileAndContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;@end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样，在“&lt;strong&gt;tableView:cellForRowAtIndexPath&lt;/strong&gt;”中，我们直接拿showTitle、showContent来显示就好，不用再创建。&lt;/p&gt;
&lt;h3 id="缓存view"&gt;&lt;a href="#%e7%bc%93%e5%ad%98view" class="header-anchor"&gt;&lt;/a&gt;缓存View!
&lt;/h3&gt;&lt;p&gt;什么？缓存View？！&lt;/p&gt;
&lt;p&gt;是的，当Cell中的部分View是非常独立的，并且不便于重用的，而且“体积”非常小，在内存可控的前提下，我们完全可以将这些view缓存起来！&lt;/p&gt;
&lt;p&gt;方法当然也是将缓存的view放在Entity中~。&lt;/p&gt;
&lt;h3 id="其它"&gt;&lt;a href="#%e5%85%b6%e5%ae%83" class="header-anchor"&gt;&lt;/a&gt;其它
&lt;/h3&gt;&lt;p&gt;当然，还有其他的优化方法，简单说一说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;尽量设置Cell的view为opaque，避免GPU对Cell下面的内容也进行绘制。&lt;/li&gt;
&lt;li&gt;避免大量的图片缩放、颜色渐变等。&lt;/li&gt;
&lt;li&gt;避免同步的从网络、文件获取数据（这个是必须的=。=）&lt;/li&gt;
&lt;li&gt;用shadowPath创建阴影。&lt;/li&gt;
&lt;li&gt;尽量减少subview的数量，如多用drawRect绘制元素，替代用view显示。&lt;/li&gt;
&lt;li&gt;尽量显示“&lt;strong&gt;大小刚好合适&lt;/strong&gt;”的图片资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;总的来说，就是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缓存一切可以缓存的！就是“用空间替换时间”！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在UITableView的Delegate、DataSource方法中，减少任何不必要的操作&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="参考推荐阅读"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb" class="header-anchor"&gt;&lt;/a&gt;参考，推荐阅读
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks#tableviews" target="_blank" rel="noopener"
 &gt;25 iOS App Performance Tips &amp;amp; Tricks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://stackoverflow.com/questions/1352479/tricks-for-improving-iphone-uitableview-scrolling-performance" target="_blank" rel="noopener"
 &gt;Tricks for improving iPhone UITableView scrolling performance?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://stackoverflow.com/questions/6172158/how-can-i-speed-up-a-uitableview" target="_blank" rel="noopener"
 &gt;How can I speed up a UITableView?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>