不管是系统自带的还是自定义的UITableViewCell,对于它们合理的使用都是决定一个UITableView的性能的关键因素。应该确保以下三条:

  • UITableViewCell的重复利用:首先对象的创建,尤其是UI控件的创建,会带来性能损耗。假设在一个很短的时间内重复分配内存,比如用户滚动一个TableView的时候,如果我们可以重复利用一些之前创建的cell,而不是再次创建新的对象,这将显著提升UITableView的性能。

  • 避免对内容重新布局:当使用一个自定义的Cell时,避免在UITableView请求显示cell时针对cell的subViews重新布局(重新计算子控件的位置),要做到一次创建重复使用。

  • 使用不透明的subViews:当自定义自己的 cell时,让cell的subviews不透明。

同样的,针对UITableView的每一个Section,我们同样需要遵循上面的原则来对它的Header和Footer来重用。这两个View都是UITableViewHeaderFooterView类型。

下面我们讨论一下普通模式与缓存模式(可能不太恰当)的代码组织上的不同。

一般情况下我们可以像如下代码一样为指定的NSIndexPath创建一个UITableViewCell:

  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  2. {
  3. UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
  4. /*
  5. ...可以在此处设置 cell的相关属性或者是子控件的信息
  6. */
  7. return cell;
  8. }

我们都知道UITableView显示时仅仅会为可见部分创建UITableViewCell以及UITableViewHeaderFooterView控件,当用户滚动 UITableView而导致其显示下方或上方的Section时,它都会重绘当前区域,那么意味着每一次的滑动操作可能都导致调用cellForRowAtIndexPath方法,从上面的代码来看,每次都会创建新的cell,当用户快速滑动时,这个创建过程尤其频繁,这或许是某些应用滚动时卡顿的原因之一。

当用户向上滚动并隐藏了之前创建的Section,然后再次向下滚动显示这些section时,系统会再次创建,这就意味着,之前创建的UITableViewCell或UITableViewHeaderFooterView没有被系统很好的利用起来,这些控件有无当做垃圾回收还有待验证。

我们知道创建一个控件的代价是非常大的,系统出于这方面考虑,提供了缓存机制,在 UITableView中维持了一个队列,用于缓存之前创建过的CellView或HeaderFooterView,缓存项都有一个唯一的标识Identifier来表示。用户可以通过标识从缓存出取得对应的控件。

既然这样,就有了下面的改进方案:

  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  2. {
  3. // static修饰局部变量:可以保证局部变量只分配一次存储空间(只初始化一次)
  4. static NSString *ID = @"XXXX";
  5.  
  6. // 1.通过一个标识去缓存池中寻找可循环利用的cell
  7. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
  8.  
  9. // 2.如果没有可循环利用的cell,再行分配
  10. if (cell == nil){
  11. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
  12. }
  13. /*
  14. ...可以在此处设置 cell的相关属性或者是子控件的信息。
  15. */
  16. return cell;
  17. }

使用上面的代码可以重复利用已有的控件。看起来都很美好,但是其实这里存在重复判断,[tableView dequeueReusableCellWithIdentifier:ID]是一个很智能的方法,方法内部首先从缓存中相应标识的控件,在返回前系统会执行一次判断是否nil的操作,如果为nil,系统尝试为你创建一个。这个时候系统会做如下操作。

  1. // 1.通过一个标识去缓存池中寻找可循环利用的cell
  2. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
  3.  
  4. // 2.如果没有可循环利用的cell,调用初始化方法
  5. if (cell == nil){
  6. cell = [[UITableViewCell alloc] initWithReuseIdentifier:ID];
  7. }

既然系统有了上面的逻辑,那么我们只需要覆写 super类的initWithReuseIdentifier:方法即可,例如如下代码片段:

  1. - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier{
  2. if (self = [super initWithReuseIdentifier:reuseIdentifier]) {
  3. UIButton *button = [UIButton buttonWithType:UIButtonTypeContactAdd];
  4. [self.contentView addSubview:button];
  5. self.button = button;
  6.  
  7. [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
  8. }
  9. return self;
  10. }

这也就意味着,代码现在可以简化,下面是某个小例子中的代码片段:(这里引用的是UITableViewHeaderFooterView的代码,原理其实一样。)

  1. -(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
  2.  
  3. FriendGroupHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"FriendGroupHeaderView"];
  4.  
  5. headerView.model = self.friendGroups[section];
  6.  
  7. return headerView;
  8. }

但是系统如何根据一个标识就知道创建哪一个控件呢,这就需要我们个告知系统在从缓存中读取失败后,如何创建这些控件,以下为截取的UITableView头文件的相关方法:

  1. // Beginning in iOS 6, clients can register a nib or class for each cell.
  2. // If all reuse identifiers are registered, use the newer -dequeueReusableCellWithIdentifier:forIndexPath: to guarantee that a cell instance is returned.
  3. // Instances returned from the new dequeue method will also be properly sized when they are returned.
  4. - (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
  5. - (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
  6. - (void)registerNib:(UINib *)nib forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
  7. - (void)registerClass:(Class)aClass forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);

系统提供了两种创建指定标识控件的方法。

1、对象类:

通过对象类创建的方法会自动调用 initWithReuseIdentifier:方法,这种模式一般用于自定义控件的子控件位置不固定或者是子控件不固定的场景,例如QQ聊天消息,每一行消息的高度是不一致的,针对是否会员还有可能选择是否创建皇冠图标。

2、Nib/Xib:

这个和通过Xib方法创建控件方式一样。适用于子控件固定的场景。

我们需要主动调用这些方法中的一个来告知系统,代码片段如下:

  1. - (void)viewDidLoad {
  2.   [super viewDidLoad];
      
  3.   // 注册指定标示的控件创建方式
  4.   [self.tableView registerClass:NSClassFromString(@"FriendGroupHeaderView") forHeaderFooterViewReuseIdentifier:@"FriendGroupHeaderView"];
  5. }

UITableViewCell和UITableViewHeaderFooterView的重用的更多相关文章

  1. 用xib自定义UITableViewCell的注意事项——重用

    问题的提出: 有时候我们经常需要自定义tableView的cell,当cell里面的布局较为复杂时往往舍弃纯代码的方式而改用xib的方式进行自定义.当我们用纯代码的方式布局cell时,往往会在cell ...

  2. tableView中cell的重用机制

    如果用UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象,然而OS设备的内存是有限的,这样就将耗尽iOS设备的内存.要解决这个问题,需要提到重用UITable ...

  3. 解决UITableView中Cell重用机制导致内容出错的方法总结

    UITableView继承自UIScrollview,是苹果为我们封装好的一个基于scroll的控件.上面主要是一个个的 UITableViewCell,可以让UITableViewCell响应一些点 ...

  4. iOS开发之UITableView及cell重用

    1.UITanleview有的两种风格 一种是Plain,一种是Grouped,可以从这里设置风格: 他们样式分别如下: Plain: Grouped: 2.tableView展示数据的过程: (1) ...

  5. 解决Cell重用内容混乱的几种简单方法,有些方法会增加内存

    重用实现分析 查看UITableView头文件,会找到NSMutableArray*  visiableCells,和NSMutableDictnery* reusableTableCells两个结构 ...

  6. ios UITableView中Cell重用机制导致内容重复解决方法

    UITableView继承自UIScrollview,是苹果为我们封装好的一个基于scroll的控件.上面主要是一个个的 UITableViewCell,可以让UITableViewCell响应一些点 ...

  7. Cell的重用原理

    iOS设备的内存有限,如果用UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象的话,那将会耗尽iOS设备的内存.要解决该问题,需要重用UITableViewC ...

  8. TableViewCell重影问题

    UITableView继承自UIScrollview,是苹果为我们封装好的一个基于scroll的控件.上面主要是一个个的UITableViewCell,可以让UITableViewCell响应一些点击 ...

  9. ReactiveCocoa基础知识内容

    本文记录一些关于学习ReactiveCocoa基础知识内容,对于ReactiveCocoa相关的概念如果不了解可以网上搜索:RACSignal有很多方法可以来订阅不同的事件类型,ReactiveCoc ...

随机推荐

  1. 避免SWF被内存提取工具提取的方法

    内存工具从内存中抓取SWF一般是依靠寻找SWF的前7个字节(3个SWF文件必有的标示字节“FWS”或“CWS”或“ZWS”+4个记录该SWF文件长度的字节),所以避免被提取我们只要在加载SWF到内存后 ...

  2. svn项目导入

    svn项目导入 在已建立好svnserverserver的情况下.且所用电脑上已装好svn,在电脑上导入svn项目 首先,新建目录.点击鼠标右键,选择TortoiseSVN–>export 然后 ...

  3. Ewebeditor最新漏洞及漏洞大全

    Ewebeditor最新漏洞及漏洞大全[收集] 来源:转载作者:佚名时间:2009-06-03 00:04:26 下面文章收集转载于网络:) 算是比較全面的ewebeditor编辑器的漏洞收集,如今的 ...

  4. node.js在windows下的学习笔记(8)---进程管理Process

    process是一个全局内置对象,可以在代码中的任何位置访问此对象,这个对象代表我们的node.js代码宿主的操作系统进程对象. 使用process对象可以截获进程的异常.退出等事件,也可以获取进程的 ...

  5. web运维第一篇:nginx配置文件详解笔记

    #定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数. worker_processes 8; #全局错误日志定义类型,[ debug | ...

  6. LVS NAT模型

    1,环境 VMWare10, CentOS6.3 2,LVS NAT网络规划 可以看到Director机器有2个IP,也就是说需要2张网卡:Real Server只需要一个网卡. VIP: 虚拟IP, ...

  7. 【ZT】修复iCloud中查找我的iPhone、查找我的iPad无法显示地图的方法

    http://blog.sina.com.cn/s/blog_4ff28d30010118cm.html 进入C:\Windows\System32\drivers\etc在hosts文件里加入如下地 ...

  8. 带缓冲I/O和不带缓冲I/O的区别与联系

    转自:http://blog.csdn.net/lmh12506/article/details/6803847 首先要明白不带缓冲的概念:所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用, ...

  9. HDU 4259 - Double Dealing(求循环节)

    首先将扑克牌进行一次置换,然后分解出所有的循环节,所有循环节的扑克牌个数的最小公倍数即为答案 #include <stdio.h> #include <string.h> #i ...

  10. java跨平台性分析

    实不相瞒,Java是我见过的执行效率最低的程序设计语言,前不久在CSDN论坛上有个评测,计算9999的阶乘,同样的循环算法,Java的耗时是.NET的5倍.我以前很喜欢Serv-U,自从它用Java重 ...