iOS中的UITableView的重用机制与加载优化详解

发布时间 - 2026-01-10 22:56:24    点击率:

UITableView可以说是UIKit中最重要的一个组件,用来展示数据列表,还可以灵活使用进行页面的布局。UITableView的使用遵循MVC模式,数据模型(NSObject)、视图(UIView)和控制器(UITableViewController)分离。UITableView继承自UIScrollView,可上下滑动,可以作为跟视图也可以作为子视图组件。

reuseIdentifier顾名思义是一个复用标识符,是一个自定义的独一无二的字符串,用来唯一地标记某种重复样式的可复用UITableViewCell,系统是通过reuseIdentifier来确定已经创建了的指定样式的cell来进行复用,iOS中表格的cell通过复用来提高加载效率,因为多数情况下表格中的cell样式都是重复的,只是数据模型不同而已,因此系统可以在保证创建足够数量的cell铺满屏幕的前提下,通过保存并重复使用已经创建的cell来提高加载效率和优化内存,避免不停地创建和销毁cell元素。

UITableViewCell的复用原理其实很简单,可以通过下面一个简单的例子来理解:

首先在开发中我们在UITableViewController类中写cell复用代码的最基本模板会像下面这样:

/**
 * 可复用cell制作
 */
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  // 定义cell重用的静态标志符
  static NSString *cell_id = @"cell_id_demo";
  // 优先使用可复用的cell
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cell_id];
  // 如果要复用的cell还没有创建,则创建一个供之后复用
  if (cell == nil) {
    // 新创建cell并使用cell_id复用符标记
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id];
  }
  // 配置cell数据
  cell.textLabel.text = [NSString stringWithFormat:@"Cell%i", countNumber];
  // 其他cell设置...
  return cell;
}

代码这样写的原因是通过调用当前tableView的dequeueReusableCellWithIdentifier方法看指定的reuseIdentifier是否有可以重复使用的了,如果有则会返回可复用的cell,cell就绪之后便可以开始更新cell的数据;如果还不可复用,则返回nil,然后会进入后面的if语句,此时创建新的cell并对其设置cell样式标记reuseIdentifier。注意上面的if语句并不是只要执行一次创建一次新的cell就完成任务,然后之后全部重复利用新创建的那一个cell,这是对cell复用机制的误解。

事实是要创建足够数量的可覆盖整个tableView的可复用cell之后才会开始复用之前的(UITableView中有一个visiableCells数组保存当前屏幕可见的cell,还有一个reusableTableCells数组用来保存那些可复用的cell),这个我们用下面的测试来验证。

如何简洁清楚的展示UITableViewCell的复用机制呢?这里的方法是创建最基本的文本cell,并创建一个cell创建计数器,每次新创建cell计数器加1并显示在cell上,如果是复用的cell则会显示是复用的哪一个cell,测试代码如下:

/**
 * 分区个数设置为1
 */
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  return 1;
}

/**
 * 创建20个cell,保证覆盖并超出整个tableView
 */
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return 20;
}

/**
 * cell复用机制测试
 */
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  // 定义cell重用的静态标志符
  static NSString *cell_id = @"cell_id_demo";
  // 计数用
  static int countNumber = 1;
  // 优先使用可复用的cell
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cell_id];
  // 如果要复用的cell还没有创建,则创建一个供之后复用
  if (cell == nil) {
    // 新创建cell并使用cell_id复用符标记
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id];
    // 计数器标记新创建的cell
    cell.textLabel.text = [NSString stringWithFormat:@"Cell%i", countNumber];
    // 计数器递增
    countNumber++;
  }
  return cell;
}

运行在iPhone5S设备上(UITableViewController作为跟控制器,tableView覆盖整个屏幕),20个cell显示结果依次为:

Cell1、Cell2、Cell3、Cell4、Cell5、Cell6、Cell7、Cell8、Cell9、Cell10、Cell11、Cell12、Cell13、Cell14、Cell1、Cell2、Cell3、Cell4、Cell5、Cell6

可以看出一共创建了14个cell,其中整个屏幕可显示13个cell,系统多创建一个的原因是保证在表格滑动显示半个cell时仍然能覆盖整个tableView。之后的6个cell就是复用了开始创建的那6个cell了。这样UITableViewCell复用的基本机制就很清楚了,另外还会有reloadData或者reloadRowsAtIndex等刷新表格数据的情况,可能会伴随新的cell创建和可复用cell的更新,但也是建立在基本复用机制的基础之上的。

能否在一个视图控制器中嵌入两个tableview控制器?

可以,相当于视图以及视图控制器的嵌套,视图可以添加子视图,视图控制器也可以添加子控制器。这么问应该是因为这种情况有时会用到而且很重要,因为有一点容易被忽视,就是将子视图添加到了父视图却忘记将对应的控制器作为子控制器添加到父控制器,导致子视图能显示但是不能响应(没有对接好控制器)。例如在当前视图上放一个小尺寸的表格组件,也就是在UIViewController上添加一个UITableViewController子控制器及其子view:

  // 假设有三个视图控制器,一个作为父控制器,两个作为子控制器
  UIViewController *superVC = [[UIViewController alloc]init];
  UITableViewController *subVC1 = [[UITableViewController alloc]init];
  UITableViewController *subVC2 = [[UITableViewController alloc]init];

  // 将子视图控制器添加到父视图控制器(要注意调整子视图的尺寸和位置合理显示,这里忽略)
  [superVC.view addSubview:subVC1.view];
  [superVC addChildViewController:subVC1];

  [superVC.view addSubview:subVC2.view];
  [superVC addChildViewController:subVC2];

  // 子视图控制器的移除有对称的方法,但只能是子视图控制器主动从父视图控制器中移除
  [subVC1.view removeFromSuperview];
  [subVC1 removeFromParentViewController];

  [subVC2.view removeFromSuperview];
  [subVC2 removeFromParentViewController];

此外要注意和presentViewController函数添加子视图控制器的区别,上面手动添加子视图控制器是可以自由调整子视图的frame的(包括子视图位置和尺寸),而presentViewController是用于页面切换,切换后的子页面会覆盖整个屏幕而不可以自由调整子页面位置和尺寸,对称的子视图控制器移除方法为dismissViewControllerAnimated:

  // 显示子视图控制器,completion后的代码块如果不为空添加结束后会触发
  [[parentVC presentViewController:childVC animated:NO completion:nil];
  // 移除子视图控制器,completion后的代码块如果不为空添加结束后会触发
  [childVC dismissViewControllerAnimated:NO completion:nil];

一个tableView是否可以关联两个不同的datasource数据源?如何处理?

多个数据源是完全可以的,关键是如何关联,问题的重点是如何处理,因为将数据源(Model)和tableview视图(View)的对接工作是程序员完成的,因此数据源的多少没有根本影响。处理上可以分开依次对接,也可以通过数据的集合操作先将数据整理合并成一个数据源然后对接。

例如:一个表格中的每个cell显示的是一个人的基本信息,为了简单这里假设只有一个头像和一个姓名。假设有两个数据源,一个数据源是头像的url数组,一个是姓名的字符串数组,对接时完全可以分开在cell数据回调中对接,也可以将两个数组合并然后对接。

合并数据用到的数据模型:

@interface Model : NSObject

@property (nonatomic,copy) NSString *name; // 姓名
@property (nonatomic,copy) NSString *url;  // 图片

@end

数据源缓冲器:

// 数据源
@property (nonatomic, strong)NSArray *name_datasource;
@property (nonatomic, strong)NSArray *url_datasource;
@property (nonatomic, strong)NSMutableArray *datasource;

处理多数据源:

/**
 * 请求数据
 */
- (void)request {
  // 姓名数据源
  _name_datasource = @[@"张三", @"李四", @"小明", @"小李"];
  _url_datasource = @[@"male", @"male", @"male", @"male"];

  // 合并数据源
  for (int i; i<_name_datasource.count; i++) {
    Model *model = [[Model alloc]init];
    model.name = _name_datasource[i];
    model.url = _url_datasource[i];
    [_datasource addObject:model];
  }
}

数据对接:

/**
 * cell数据回调
 */
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  static NSString *identifier = @"identifier";
  // 自制cell组件
  AccountCell *cell = [[AccountCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];

  /** 多数据源分开对接:**/
  // 头像
  [cell.avatar setImage:[UIImage imageNamed:_url_datasource[indexPath.row]]];
  // 姓名
  cell.name.text = _name_datasource[indexPath.row];

  // 或者:

  /** 数据源合并后对接**/
  // 取出对应数据模型
  Model *model = _datasource[indexPath.row];
  // 头像
  [cell.avatar setImage:[UIImage imageNamed:model.url]];
  // 姓名
  cell.name.text = model.name;

  return cell;
}

如何对UITableView的滚动加载进行优化,防止卡顿?

UITableView的滚动优化主要在于以下两个方面:

  • 减少cellForRowAtIndexPath代理中的计算量(cell的内容计算)
  • 减少heightForRowAtIndexPath代理中的计算量(cell的高度计算)

减少cellForRowAtIndexPath代理中的计算量

  • 首先要提前计算每个cell中需要的一些基本数据,代理调用的时候直接取出;
  • 图片要异步加载,加载完成后再根据cell内部UIImageView的引用设置图片;
  • 图片数量多时,图片的尺寸要跟据需要提前经过transform矩阵变换压缩好(直接设置图片的contentMode让其自行压缩仍然会影响滚动效率),必要的时候要准备好预览图和高清图,需要时再加载高清图。
  • 图片的‘懒加载'方法,即延迟加载,当滚动速度很快时避免频繁请求服务器数据。
  • 尽量手动Drawing视图提升流畅性,而不是直接子类化UITableViewCell,然后覆盖drawRect方法,因为cell中不是只有一个contentview。绘制cell不建议使用UIView,建议使用CALayer。原因要参考UIView和CALayer的区别和联系。

减少heightForRowAtIndexPath代理中的计算量

  • 由于每次TableView进行update更新都会对每一个cell调用heightForRowAtIndexPath代理取得最新的height,会大大增加计算时间。如果表格的所有cell高度都是固定的,那么去掉heightForRowAtIndexPath代理,直接设置TableView的rowHeight属性为固定的高度;
  • 如果高度不固定,应尽量将cell的高度数据计算好并储存起来,代理调用的时候直接取,即将height的计算时间复杂度降到O(1)。例如:在异步请求服务器数据时,提前将cell高度计算好并作为dataSource的一个数据存到数据库供随时取用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# uitableview重用机制  # ios重用机制  # ios  # cell的重用机制  # 详解优化iOS程序性能的25个方法  # h5 ios输入框和键盘的兼容性优化指南  # iOS性能优化教程之页面加载速率详解  # 阿里数据iOS端启动速度优化心得  # iOS体验性优化之RTL适配右滑返回的实现  # IOS开发中加载大量网络图片优化方法  # 详细整理iOS中UITableView的性能优化  # iOS开发中UITableview控件的基本使用及性能优化方法  # iOS程序性能优化的技巧  # 复用  # 加载  # 创建一个  # 移除  # 都是  # 是一个  # 还没有  # 可以通过  # 要注意  # 只有一个  # 后会  # 则会  # 回调  # 如何处理  # 为空  # 的是  # 重复使用  # 这是  # 器中  # 是在 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel如何优化应用性能?(缓存和优化命令)  敲碗10年!Mac系列传将迎来「触控与联网」双革新  Android实现代码画虚线边框背景效果  Laravel怎么使用Intervention Image库处理图片上传和缩放  Laravel如何使用Service Container和依赖注入?(代码示例)  百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧  Laravel如何将应用部署到生产服务器_Laravel生产环境部署流程  网站优化排名时,需要考虑哪些问题呢?  Win11怎么查看显卡温度 Win11任务管理器查看GPU温度【技巧】  高防服务器如何保障网站安全无虞?  Laravel Eloquent模型如何创建_Laravel ORM基础之Model创建与使用教程  微信推文制作网站有哪些,怎么做微信推文,急?  香港服务器网站推广:SEO优化与外贸独立站搭建策略  如何快速打造个性化非模板自助建站?  Laravel如何获取当前登录用户信息_Laravel Auth门面使用与Session用户读取【技巧】  bootstrap日历插件datetimepicker使用方法  Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧  韩国服务器如何优化跨境访问实现高效连接?  Laravel如何实现用户注册和登录?(Auth脚手架指南)  大连网站制作公司哪家好一点,大连买房网站哪个好?  js代码实现下拉菜单【推荐】  品牌网站制作公司有哪些,买正品品牌一般去哪个网站买?  html5如何设置样式_HTML5样式设置方法与CSS应用技巧【教程】  详解Oracle修改字段类型方法总结  Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】  如何快速建站并高效导出源代码?  如何在IIS中配置站点IP、端口及主机头?  Bootstrap CSS布局之列表  公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?  Laravel如何实现API速率限制?(Rate Limiting教程)  laravel怎么用DB facade执行原生SQL查询_laravel DB facade原生SQL执行方法  如何在建站主机中优化服务器配置?  企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?  Python3.6正式版新特性预览  详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)  Laravel如何配置.env文件管理环境变量_Laravel环境变量使用与安全管理  瓜子二手车官方网站在线入口 瓜子二手车网页版官网通道入口  Laravel如何实现本地化和多语言支持?(i18n教程)  Laravel项目怎么部署到Linux_Laravel Nginx配置详解  rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted  如何在IIS7中新建站点?详细步骤解析  如何生成腾讯云建站专用兑换码?  如何在阿里云域名上完成建站全流程?  高端网站建设与定制开发一站式解决方案 中企动力  JavaScript如何实现错误处理_try...catch如何捕获异常?  Laravel如何实现图片防盗链功能_Laravel中间件验证Referer来源请求【方案】  JavaScript如何实现类型判断_typeof和instanceof有什么区别  如何在云虚拟主机上快速搭建个人网站?  html文件怎么打开证书错误_https协议的html打开提示不安全【指南】  如何用y主机助手快速搭建网站?