UITableView优化思路与复用

单元格复用问题    demo 点击此处下载

UITableView最核心的思想就是UITableViewCell的重用机制。简单的理解就是:UITableView一开始只会创建屏幕上要显示的Cell,每当Cell滑出屏幕时,就会放入到一个叫“重用池”的里面,滑动中当要显示某一位置的Cell时,会先去“重用池”取,如果有,就直接拿来显示;如果没有,才会创建。这样做的好处就是极大的减少了内存的开销,不需要每个都创建了。那么我们首先对复用进行一个探究(以下探究方法是当初刚学习OC时候网上受到的启发,自己按照这个思路尝试)。


// 方法1

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"qwer"];

if (!cell) {

cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"qwer"];

}


// 方法2(xib),在GLImageCell.xib文件中identifier中输入GLImageCell,在GLImageCell.m中复写initWithStyle reuseIdentifier方法  复写内容见demo(loadNibNamed的方法)

GLImageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GLImageCell"];

if (nil == cell) {

cell = [[GLImageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"GLImageCell"];

}


// 方法3

Method3Cell *cell = [tableView dequeueReusableCellWithIdentifier:@"method3"];

if (nil == cell) {

cell = [[[NSBundle mainBundle]loadNibNamed:@"Method3Cell" owner:self options:nil]lastObject];

 cell.selectionStyle=UITableViewCellSelectionStyleNone;

 [tableView registerNib:[UINib nibWithNibName:@"Method3Cell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"method3"];

}

return cell;


// 方法4

1.在viewdidload方法里面

UINib *firstNib = [UINib nibWithNibName:@"Method3Cell" bundle:nil];

[_tableView registerNib:firstNib forCellReuseIdentifier:@"Method3"];

2.在 dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath方法中

Method3Cell *cell = [tableView dequeueReusableCellWithIdentifier:@"Method3" forIndexPath:indexPath];


//在以下方法中这么写

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

static int myCount = 0;       //static变量只是在编译时候进行初始化

TableViewCell *cell;

cell = [tableView dequeueReusableCellWithIdentifier:@"test"];

if (nil == cell) {

// 此处调用方法1,2,3,4进行测试

myCount ++;

NSLog(@"count :%d ",myCount);

}

return cell;

}

测试输出结果如下

//方法1  输出为1,2,3,4,5,6,7,8,9   

//方法2  输出为1,2,3,4,5,6,7,8,9

//方法3  输出为1

//方法4  输出为               (就是不输出)


为什么会这样?首先我们大胆猜测一下是不是这样的:方法1和方法2是因为一开始都没有对应这个Identifier的cell,所以每次都进入if(cell==nil)方法里面。方法3是因为第一次没有找到对应Identifier的cell,于是进入if(cell==nil)里面,然后调用的注册方法,所以只输出一个数。方法4是因为在最开始就已经注册过,所以不会进入if里面,所以就没有输出。

我们先来看这句 dequeueReusableCellWithIdentifier 是什么意思,我们查看官方文档

网上翻译一下大致意思:

出于性能的原因,一个表视图的数据源应该采用可复用的表视图单元对象。一个表视图维护着一个可复用单元的队列或者列表。当要显示一个新的单元的时候就调用这个方法,这个方法会出列一个已经存在的单元。假如没有可以复用的单元那么就返回nil。

从表视图的生命周期来说,一开始可复用队列为空,调用dequeueReusableCellWithIdentifier:肯定返回nil。然后就调用initWithStyle:reuseIdentifier:方法来产生并且标识复用记号的表视图单元。满屏显示的时候,滚动表视图,一侧的单元就会被移出屏幕,此时这个单元进入可复用单元队列,然后调用prepareForReuse方法准备一个即将出列的单元, dequeueReusableCellWithIdentifier:从可复用单元队列里出列一个可复用单元。

//修改一下例子

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

static int myCount = 0;      //static变量只是在编译时候进行初始化

TableViewCell *cell;

cell = [tableView dequeueReusableCellWithIdentifier:@"test"];

if (nil == cell) {

// 此处调用方法1,2,3,4进行测试

myCount ++;

NSLog(@"count :%d ",myCount);

}

//第二种输出

NSLog(@"+++++++++before:%@",cell.textLabel.text);

cell.textLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row];

NSLog(@"---------after:%@",cell.textLabel.text);

输出结果

/* 方法3的第二种输出

2016-09-08 16:49:18.066 TableViewTest[43861:2739078] time : 1

2016-09-08 16:49:18.068 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.068 TableViewTest[43861:2739078] ---------after:0

2016-09-08 16:49:18.071 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.071 TableViewTest[43861:2739078] ---------after:1

2016-09-08 16:49:18.072 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.072 TableViewTest[43861:2739078] ---------after:2

2016-09-08 16:49:18.072 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.073 TableViewTest[43861:2739078] ---------after:3

2016-09-08 16:49:18.073 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.073 TableViewTest[43861:2739078] ---------after:4

2016-09-08 16:49:18.074 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.098 TableViewTest[43861:2739078] ---------after:5

2016-09-08 16:49:18.099 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.099 TableViewTest[43861:2739078] ---------after:6

2016-09-08 16:49:18.100 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.100 TableViewTest[43861:2739078] ---------after:7

2016-09-08 16:49:18.101 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:18.101 TableViewTest[43861:2739078] ---------after:8

2016-09-08 16:49:19.966 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:19.966 TableViewTest[43861:2739078] ---------after:9

2016-09-08 16:49:32.542 TableViewTest[43861:2739078] +++++++++before:(null)

2016-09-08 16:49:32.543 TableViewTest[43861:2739078] ---------after:10

2016-09-08 16:49:37.408 TableViewTest[43861:2739078] +++++++++before:1

2016-09-08 16:49:37.409 TableViewTest[43861:2739078] ---------after:11

*/

/*方法1,方法2,方法4的第二种输出

2016-09-08 16:53:31.731 TableViewTest[43916:2742025] time : 1

2016-09-08 16:53:31.734 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.735 TableViewTest[43916:2742025] ---------after:0

2016-09-08 16:53:31.736 TableViewTest[43916:2742025] time : 2

2016-09-08 16:53:31.736 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.737 TableViewTest[43916:2742025] ---------after:1

2016-09-08 16:53:31.737 TableViewTest[43916:2742025] time : 3

2016-09-08 16:53:31.738 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.738 TableViewTest[43916:2742025] ---------after:2

2016-09-08 16:53:31.738 TableViewTest[43916:2742025] time : 4

2016-09-08 16:53:31.738 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.739 TableViewTest[43916:2742025] ---------after:3

2016-09-08 16:53:31.739 TableViewTest[43916:2742025] time : 5

2016-09-08 16:53:31.740 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.740 TableViewTest[43916:2742025] ---------after:4

2016-09-08 16:53:31.740 TableViewTest[43916:2742025] time : 6

2016-09-08 16:53:31.741 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.741 TableViewTest[43916:2742025] ---------after:5

2016-09-08 16:53:31.742 TableViewTest[43916:2742025] time : 7

2016-09-08 16:53:31.742 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.742 TableViewTest[43916:2742025] ---------after:6

2016-09-08 16:53:31.743 TableViewTest[43916:2742025] time : 8

2016-09-08 16:53:31.743 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.744 TableViewTest[43916:2742025] ---------after:7

2016-09-08 16:53:31.744 TableViewTest[43916:2742025] time : 9

2016-09-08 16:53:31.745 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:31.745 TableViewTest[43916:2742025] ---------after:8

2016-09-08 16:53:38.519 TableViewTest[43916:2742025] time : 10

2016-09-08 16:53:38.519 TableViewTest[43916:2742025] +++++++++before:(null)

2016-09-08 16:53:38.519 TableViewTest[43916:2742025] ---------after:9

2016-09-08 16:53:38.604 TableViewTest[43916:2742025] +++++++++before:0

2016-09-08 16:53:38.605 TableViewTest[43916:2742025] ---------after:10

2016-09-08 16:53:38.688 TableViewTest[43916:2742025] +++++++++before:1

2016-09-08 16:53:38.689 TableViewTest[43916:2742025] ---------after:11

2016-09-08 16:53:38.740 TableViewTest[43916:2742025] +++++++++before:2

2016-09-08 16:53:38.740 TableViewTest[43916:2742025] ---------after:12

2016-09-08 16:53:38.808 TableViewTest[43916:2742025] +++++++++before:3

2016-09-08 16:53:38.808 TableViewTest[43916:2742025] ---------after:13

*/

方法3中,可复用的cell的text从1开始,从xib中创建的第一个cell,它的identify还没有注册,所以identify值没有,不能复用。复用是从第二个单元格开始

方法1,方法2,方法4中,可复用的cell从第一个单元格开始。

以上的结果跟我们一开始的猜测应该是一样的吧!

在标题边上有github的demo下载地址,有需要的朋友可以下载来看一下   demo下载


UITableView优化问题   

在实际开发中,有时候只使用单元格复用还是不够的,在滑动的时候也同样会有卡顿感,那么我们该怎么办呢?

以下是天哥根据多年网上的学习归纳总结的几点,不对的地方大家也请点出来:

1.单元格复用

这个我们前面探究的时候说过了,单元格复用能有效的减少内存的消耗。单元格复用的实现是最简单也是最重要的。

2.使用异步网络请求,缓存图片或者数据

如果我们要显示的内容跟之前的一样,每次都要从网上获取,是不是很浪费呢?我们要减少数据加载和逻辑处理的时间,从内存中获取就大大减少了这方面的消耗,我们要将。现在被大家广泛使用的AFNetworking就是异步网络请求。

3. 提前计算并缓存Cell的高度

在UITableView中,运行时首先调用的方法是”tableView:heightForRowAtIndexPath:”,如果我们在这个方法里面进行各种计算什么的,将会大大增加手机的负担。所以,我们尽量能不调用代理方法的就不要调用(在创建tableview的时候使用tableview.rowHeight来确定cell的高度)。必须调用的情况下(比如每个section所对应的cell不一样等等)也尽量不要在”tableView:heightForRowAtIndexPath:”这个方法中进行一系列的计算,而是提前计算好,直接将计算好的值使用到这个方法中。如果必须要在方法里面计算,那要尽力做最简单的计算。

4.异步绘制

在自定义的cell中,假设工作需求里面,我们要将cell上的某个Button的边框变成红色并且边框要有一点圆弧形。这个时候,我们对view外形的操作最好都放在类似于drawRect的方法里面。drawRect方法是异步绘制,可以大大的缓解主线程的压力。

5.滑动时,按需加载。图片加载时,异步加载(SDWebImage已经实现了异步加载图片,有兴趣的朋友可以去研究一下)。

大家考虑一个场景,如果我们滑动的很快,中间那些瞬间而过的内容是不是不需要显示?哪怕显示了也没用对吧?滑动的那么快我们怎么看得清呢?所以,按需加载就很重要了。我举的例子只是其中的一种情况,这里的“需”每个人都各不一样,有些可能是希望当滑动停止的时候才加载图片(如安卓版本的饿了么。但是iOS版本的饿了么并没有做任何处理,版本低点的iphone手机就会有卡顿感)还有百度外卖的滑动显示逻辑又不一样了(安卓版本的百度外卖在滑动时,要显示下一个图片就慢慢加载出来。iOS版本就没做这个处理,同样老一点的iphone手机也有卡顿感)这里卡顿感我都是用iphone5体验到的,我用iphone6s点外卖就没有卡顿感。按需加载最重要的一点是,我们充分利用好以下方法(并不绝对,看每个人的需求)

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

这些方法思想就是识别UITableView禁止或者减速滑动结束的时候,进行异步加载图片,快滑动过程中,只加载目标范围内的Cell,这样按需加载,极大的提高流畅度。而SDWebImage可以实现异步加载,与这条性能配合就完美了,尤其是大量图片展示的时候。

6.尽量减少subviews的个数和层级

有时候根据需求,我们不得不在cell上面添加button,image,label,textview等等。必须要加的东西,我们还是要加,但是我们尽量想办法减少这些层级数量。如果我们对某个view设置了alpha < 1,我们应该将这个view的opaque属性设置为NO。如果alpha = 1,则设置opaque为YES。默认情况下opaque为YES。  原因见官方文档,英语大致意思相对简单,我就不翻译了。

 

关于优化tableview的demo是我在以前学习网上知识下的demo上进行了修改并且增加了注释后完成的,便于大家理解。优化方法很多,这并不是最好的优化,但是可以给大家做一个参考。      优化demo下载     

感谢大家的支持,zty能力有限。也感谢那些网上给予参考和启发的文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容