在iOS开发中, UITableView是最常用到的复杂控件. 使用不难, 但想用好却不容易. 需要考虑到后台数据的设计, tableViewCell的设计和优化, 以及tableView的效率等问题.
本文主要介绍一下UITableView的常见优化技巧, 主要参考博客:
VVeboTableViewDemo.
tableView的优化主要思路是:
1. 异步渲染内容到图片。
2. 按照滑动速度按需加载内容。
3. 重写处理网络图片加载。
4. 缓存一切可缓存的, 用空间换时间.
重用cell
UITableViewCell的重用机制是最常见也是最有效的优化方式之一.
使用dequeueReusableCellWithIdentifier来实现布局相同的cell的重用, 也可以通过cellForRowAtIndexPath 直接复用某个cell. 如
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
WeiboTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[WeiboTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
[self drawCell:cell withIndexPath:indexPath];
return cell;
]
因为cellForRowAtIndexPath方法调用非常频繁,初始化,上下滚动,刷新都要调用. 所以cell的标示声明为静态变量更好。
static NSString *cellIdentifier = @“Tracks”;
避免cell的重新布局
cell的布局填充等操作比较耗时, 一般可在创建时就布局好. 如可将cell单独放到一个class中WeiboTableViewCell, 重写其initWithStyle方法, 在其中将cell的布局设置完成.
创建cell完成之后, 调用drawCell往其中填充内容即可, 即将cell的布局及填充分开执行, 且尽量将要填充的data提前准备好.
- (void)drawCell:(WeiboTableViewCell *)cell withIndexPath:(NSIndexPath *)indexPath {
NSDictionary *data = [datas objectAtIndex:indexPath.row]; // 提前缓存好cell中的内容
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell clear];
cell.data = data;
if (needLoadArr.count>0 && [needLoadArr indexOfObject:indexPath]==NSNotFound) {
[cell clear];
return;
}
if (scrollToToping) {
return;
}
[cell draw];
}
提前计算并缓存cell的属性及内容
因UITableView继承自UIScrollView, 因此其布局主要表现为Plain和Grouped两种风格. 需先确定其contentSize及每个cell的位置, 才会将其放进去. 如:
要显示100个cell,而当前屏幕只能显示5个. 则reload的时候,会先调用100次heightForRowAtIndexPath方法, 然后调用5次cellForRowAtIndexPath方法; 滚动屏幕时, 每当cell进入屏幕, 都会调用一次heightForRowAtIndexPath和cellForRowAtIndexPath方法.
- cellForRowAtIndexPath和heightForRowAtIndexPath是调用最频繁的方法, 要尽量少地调用这两个方法.
cell填充与计算布局分离.
cellForRowAtIndexPath只填充cell,
heightForRowAtIndexPath负责计算高度, 将高度等布局缓存到数据源中.对于富文本AttributedString等cell中内容, 可提前创建好, 进行数据缓存, 然后需要时直接往cell中填充即可.
使用estimatedRowHeight来预估高度, 防止浪费时间计算屏幕外边的cell, 如
self.tableView.estimatedRowHeight = 88.
cell内容的异步加载
如web的内容异步加载, 图片配合SDWebImage缓存, 将网络请求结果缓存.
subView的绘制
如果有多个不同风格的cell, 可以给每种cell指定不同的重用标识符. 然后使用dequeue每次将其出列使用即可. 如:
NSString *cellId = [NSString stringWithFormat:@“Cell%d%d”, indexPath.section, indexPath.row];
- 少用addView给cell动态添加view, 减少创建subview的数量如cell大致布局相同, 则可以只定义一种cell, 在初始化时添加, 通过hidden来控制其中内容的显示. 如有可能, 尽量缓存subview.
- 慎用clearColor, 多个view层叠加渲染会消耗更多的时间, 所以尽量不要或者少用透明图层, 因系统将透明层与下面的view混合起来计算颜色, 渲染速度. 所以, 慎重使用clearColor.
- 尽量将opaque设为YES, 尽量将subview的opaque设为YES, 避免GPU对cell其中的内容进行绘制.
- 避免无用的CALayer渲染特效.
- 需要绘制阴影的时候,通过指定阴影的路径提高效率.
- 重载subView的drawRect方法如果定制cell的过程中需要多个小的元素的话,最好直接对要显示的多个项目进行绘制,而非采用添加多个subview.
UITableView的局部更新
我们常用[self.tableView reloadData]来进行tableView中的数据更新. 如果只是更新某个section的话, 可以使用reloadSections等进行局部的更新
self.tableView reloadRowsAtIndexPaths:<#(NSArray *)#> withRowAnimation:<#(UITableViewRowAnimation)#>
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
tableView的异步绘制
对于复杂的tableView界面, 可考虑异步绘制. 使用dispatch_async和dispatch_sync配合, 将业务逻辑与UI绘制分开. 如:
//异步绘制
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGRect rect = [_data[@"frame"] CGRectValue];
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
//整个内容的背景
[[UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1] set];
CGContextFillRect(context, rect);
//转发内容的背景
if ([_data valueForKey:@"subData"]) {
[[UIColor colorWithRed:243/255.0 green:243/255.0 blue:243/255.0 alpha:1] set];
CGRect subFrame = [_data[@"subData"][@"frame"] CGRectValue];
CGContextFillRect(context, subFrame);
[[