优化tableView性能(针对滑动时出现卡的现象)

(2013-08-02 11:18:15)

转载▼

标签:

ios

tableview

it

分类: 技术文档

在iOS应用中,UITableView应该是使用率最高的视图之一了。iPod、时钟、日历、备忘录、Mail、天气、照片、电话、短信、 Safari、App Store、iTunes、Game Center⋯几乎所有自带的应用中都能看到它的身影,可见它的重要性。 
然而在使用第三方应用时,却经常遇到性能上的问题,普遍表现在滚动时比较卡,特别是table cell中包含图片的情况时。 
实际上只要针对性地优化一下,这种问题就不会有了。有兴趣的可以看看 LazyTableImages这个官方的例子程序,虽然也要从网上下载图片并显示,但滚动时丝毫不卡。 
下面就说说我对UITableView的了解。不过由于我也是初学者,或许会说错或遗漏一些,因此仅供参考。

首先说下UITableView的原理。有兴趣的可以看看 《About Table Views in iOS-Based Applications》。 
UITableView是UIScrollView的子类,因此它可以自动响应滚动事件(一般为上下滚动)。 
它 内部包含0到多个UITableViewCell对象,每个table cell展示各自的内容。当新cell需要被显示时,就会调用tableView:cellForRowAtIndexPath:方法来获取或创建一个 cell;而不可视时,它又会被释放。由此可见,同一时间其实只需要存在一屏幕的cell对象即可,不需要为每一行创建一个cell。 
此 外,UITableView还可以分为多个sections,每个区段都可以有自己的head、foot和cells。而在定位一个cell时,就需要2 个字段了:在哪个section,以及在这个section的第几行。这在iOS SDK中是用NSIndexPath来表述的,UIKit为其添加了indexPathForRow:inSection:这个创建方法。 
其他诸如编辑之类的就不提了,因为和本文无关。

介绍完原理,接下来就开始优化吧。

  1. 使用不透明视图。
    不透明的视图可以极大地提高渲染的速度。因此如非必要,可以将table cell及其子视图的opaque属性设为YES(默认值)。
    其中的特例包括背景色,它的alpha值应该为1(例如不要使用clearColor);图像的alpha值也应该为1,或者在画图时设为不透明。
  2. 不要重复创建不必要的table cell
    前面说了,UITableView只需要一屏幕的UITableViewCell对象即可。因此在cell不可见时,可以将其缓存起来,而在需要时继续使用它即可。
    而UITableView也提供了这种机制,只需要简单地设置一个identifier即可:

static NSString *CellIdentifier = @"xxx"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; }

值得一提的是,cell被重用时,它内部绘制的内容并不会被自动清除,因此你可能需要调用setNeedsDisplayInRect:或setNeedsDisplay方法。
此 外,在添加table cell的时候,如果不需要动画效果,最好不要使用insertRowsAtIndexPaths:withRowAnimation:方法,而是直接调 用reloadData方法。因为前者会对所有indexPaths调用tableView:cellForRowAtIndexPath:方法,即便该 cell并不需要显示(不知道是不是bug),这就可能创建大量多余的cell。勘误:只是在模拟器上测试如此,真机调试时没有这种bug。

  1. 减少视图的数目。
    UITableViewCell包含了textLabel、detailTextLabel和imageView等view,而你还可以自定义一些视图放在它的contentView里。然而view是很大的对象,创建它会消耗较多资源,并且也影响渲染的性能。
    如果你的table cell包含图片,且数目较多,使用默认的UITableViewCell会非常影响性能。奇怪的是,使用自定义的view,而非预定义的view,明显会快些。
    当然,最佳的解决办法还是继承UITableViewCell,并在其drawRect:中自行绘制:

- (void)drawRect:(CGRect)rect { if (image) { [image drawAtPoint:imagePoint]; self.image = nil; } else { [placeHolder drawAtPoint:imagePoint]; } [text drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeTailTruncation]; }

不过这样一来,你会发现选中一行后,这个cell就变蓝了,其中的内容就被挡住了。最简单的方法就是将cell的selectionStyle属性设为UITableViewCellSelectionStyleNone,这样就不会被高亮了。
此 外还可以创建CALayer,将内容绘制到layer上,然后对cell的contentView.layer调用addSublayer:方法。这个例 子中,layer并不会显著影响性能,但如果layer透明,或者有圆角、变形等效果,就会影响到绘制速度了。解决办法可参见后面的预渲染图像。

  1. 不要做多余的绘制工作。
    在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。
    例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。
  2. 预渲染图像。
    你会发现即使做到了上述几点,当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕,详细做法可见《利用预渲染加速iOS设备的图像显示》
  3. 不要阻塞主线程。
    做到前几点后,你的table view滚动时应该足够流畅了,不过你仍可能让用户感到不爽。常见的现象就是在更新数据时,整个界面卡住不动,完全不响应用户请求。
    出现这种现象的原因就是主线程执行了耗时很长的函数或方法,在其执行完毕前,无法绘制屏幕和响应用户请求。其中最常见的就是网络请求了,它通常都需要花费数秒的时间,而你不应该让用户等待那么久。
    解 决办法就是使用多线程,让子线程去执行这些函数或方法。这里面还有一个学问,当下载线程数超过2时,会显著影响主线程的性能。因此在使用 ASIHTTPRequest时,可以用一个NSOperationQueue来维护下载请求,并将其 maxConcurrentOperationCount设为2。而NSURLRequest则可以配合GCD来实现,或者使用NSURLConnection的setDelegateQueue:方法。
    当然,在不需要响应用户请求时,也可以增加下载线程数,以加快下载速度:

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { queue.maxConcurrentOperationCount = 5; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { queue.maxConcurrentOperationCount = 5; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { queue.maxConcurrentOperationCount = 2; }

此外,自动载入更新数据对用户来说也很友好,这减少了用户等待下载的时间。例如每次载入50条信息,那就可以在滚动到倒数第10条以内时,加载更多信息:

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if (count - indexPath.row < 10 && !updating) { updating = YES; [self update]; } }// update方法获取到结果后,设置updating为NO

还有一点要注意的就是当图片下载完成后,如果cell是可见的,还需要更新图像:

NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];for (NSIndexPath *visibleIndexPath in indexPaths) { if (indexPath == visibleIndexPath) { MyTableViewCell *cell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; cell.image = image; [cell setNeedsDisplayInRect:imageRect]; break; } }// 也可不遍历,直接与头尾相比较,看是否在中间即可。

最后还是前面所说过的insertRowsAtIndexPaths:withRowAnimation:方法,插入新行需要在主线程执行,而一次插入很多行的话(例如50行),会长时间阻塞主线程。而换成reloadData方法的话,瞬间就处理完了。

最新文章

  1. &lt;&lt;&lt; web里面Servlet高级应用的基础介绍
  2. JavaScript DOM编程艺术读书笔记(四)
  3. ALinq Dynamic 使用指南——慨述(上)
  4. Qt——树的搜索
  5. 详细剖析电脑hosts文件的作用和修改
  6. GUID转换成16位字符串或19位数据(确保唯一)
  7. boost.asio源码剖析(四) ---- asio中的泛型概念(concepts)
  8. Frogs&#39; Neighborhood
  9. Python生成器与yield
  10. 【数学三角恒等变幻】【HDU2552】三足鼎立
  11. BNU Box of Bricks
  12. python自动化框架(unnitest+selenium+htmlreport)
  13. ●BZOJ 4289 PA2012 Tax
  14. viewport定义,弹性布局,响应式布局及LESS和SASS框架应用
  15. GIT刷新忽略文件.gitignore
  16. 关于vs设置其他主题配色问题
  17. 取消掉maven
  18. normalized
  19. JavaScript基础(一)概述
  20. 一个分布式 MySQL Binlog 存储系统的架构设计

热门文章

  1. iOS界面的绘制和渲染
  2. linux Bash
  3. java内存分析
  4. 文件上传之Html5 + jQuery上传、asp.net web api接收
  5. JS 菜单栏一直悬浮在顶部代码
  6. WINDOWS的NTP配置
  7. 在updatepanel使用colorbox无效
  8. Daily Scrum 12.2
  9. Mac上Homebrew的使用 (Homebrew 使 OS X 更完整)
  10. js之函数