• TableView滑动时候出现的卡顿现象


    分析

    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

    3.    减少视图的数目。
    UITableViewCell包含了textLabeldetailTextLabelimageViewview,而你还可以自定义一些视图放在它的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:fontlineBreakMode:UILineBreakModeTailTruncation]; }

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

    4.    不要做多余的绘制工作。
    在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。
    例如上例中,就可以用CGRectIntersectsRectCGRectIntersectionCGRectContainsRect判断是否需要绘制imagetext,然后再调用绘制方法。

    5.    预渲染图像。
    你会发现即使做到了上述几点,当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕,详细做法可见《利用预渲染加速iOS设备的图像显示》

    6.    不要阻塞主线程。
    做到前几点后,你的table view滚动时应该足够流畅了,不过你仍可能让用户感到不爽。常见的现象就是在更新数据时,整个界面卡住不动,完全不响应用户请求。
    出现这种现象的原因就是主线程执行了耗时很长的函数或方法,在其执行完毕前,无法绘制屏幕和响应用户请求。其中最常见的就是网络请求了,它通常都需要花费数秒的时间,而你不应该让用户等待那么久。
    决办法就是使用多线程,让子线程去执行这些函数或方法。这里面还有一个学问,当下载线程数超过2时,会显著影响主线程的性能。因此在使用ASIHTTPRequest时,可以用一个NSOperationQueue来维护下载请求,并将其maxConcurrentOperationCount设为2。而NSURLRequest则可以配合GCD来实现,或者使用NSURLConnectionsetDelegateQueue:方法
    当然,在不需要响应用户请求时,也可以增加下载线程数,以加快下载速度:

    - (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 *)cellforRowAtIndexPath:(NSIndexPath *)indexPath { if (count - indexPath.row < 10&& !updating) { updating = YES; [self update]; } }// update方法获取到结果后,设置updating为NO

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

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

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

  • 相关阅读:
    如何在iTerm2中配置oh my zsh?
    sublime中格式化jsx文件
    ES6 new syntax of Literal
    ES6 new syntax of Rest and Spread Operators
    How to preview html file in our browser at sublime text?
    ES6 new syntax of Default Function Parameters
    ES6 new syntax of Arrow Function
    七牛云2018春招笔试题
    Spring-使用注解开发(十二)
    Spring-声明式事物(十一)
  • 原文地址:https://www.cnblogs.com/crazygeek/p/6385321.html
Copyright © 2020-2023  润新知