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

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的一个数据存到数据库供随时取用。

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

(0)

相关推荐

  • iOS中UITableView使用的常见问题总结

    1.如何设置headerView以及其高度 tableView.tableHeaderView = myHeaderView let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height var frame = headerView.frame frame.size.height = height headerView.frame = frame 2.去掉多余cell的分割线 s

  • iOS中UITableView Cell实现自定义单选功能

    今天分享下cell的单选,自定义的,不是下图这种网上找到的打对勾的,我搜了好久,基本上都是打对勾的文章,就决定自己写一篇.基本上自己的app都会有一个风格吧,咱也不能一直用打对勾的方式去做(看起来是不是很low). 我们要实现的是下面的这种形式.瞬间好看了很多,高大上了很多是吧. 具体我来给大家介绍一下.我这种方法有可能不是很好,有大神来,欢迎多多交流. 首先在你自定义的cell里面加入一个UIImageView,因为你肯定要有选择和未选择两张图片的吧,所以这个UIImageView来切换图片.

  • IOS中UITableView滚动到指定位置

    方法很简单: - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated 有些需要注意的地方: 如果在reloadData后需要立即获取tableview的cell.高度,或者需要滚动tableview,那么,直接在reloadData后执行代码是有可能出问题的. reloadDa

  • ios UITableView实现无数据加载占位图片

    本文介绍了ios UITableView实现无数据占位图片,分享给大家,具体如下: 国际惯例,上效果图 该效果的实现主要是使用runtime的交叉方法实现,将tableView的reloadData与自定义的kk_reloadData交换.新建tableView的Category. 交换方法主要代码 + (void)swizzleInstanceSelector:(SEL)originalSel WithSwizzledSelector:(SEL)swizzledSel { Method ori

  • iOS基于UITableView实现多层展开与收起

    本文实例为大家分享了bleView多层展开与收起的具体代码,供大家参考,具体内容如下 规则要求: tableview 有多层,类似于xcode文件目录的层级关系,每一个最开始展示的层姑且称之为根目录吧,并且,每个根目录下的层数不定. 与文件目录类似,每个目录下可以有不同层级的目录同时展开,但是同一层次中只有一层是展开的,即要展开B层次的某一层,则需要收起B层次所有其他的层级. 最底层是一个个文件,不能再展开(这里在业务逻辑上用处是:跳转到不同的页面). 想法: 整个界面是一个tableview,

  • iOS中UIScrollView嵌套UITableView的实践教程

    前言 最近因为工作项目中需要用到UIScrollView嵌套UItableView嵌套交互问题,顺便网上搜了下的demo,发现实现的效果并不是很理想,滑动偶尔会有延迟现象,所以自己想了个办法,顺便把自己实现写了个demo分享出来,一起来看看吧. 实现过程 最底部放置的为一个UIScrollView,设置ScrollView的contentSize属性,使可以发生横向滚动,同时隐藏横向滚动条,设置代理为当前控制器本身.然后,在最底部的UIScrollView上放置2个UITableView,因为只

  • IOS UITableViewCell详解及按钮点击事件处理实例

    IOS UITableViewCell详解及按钮点击事件处理 今天突然做项目的时候,又遇到处理自定义的UITableViewCell上按钮的点击事件问题.我知道有两种方式,可是突然想不起来之前是怎么做的了,好记性不如烂笔头,还是记录一下吧. 1.第一种方式给Button加上tag值 这里分为两种:一种是直接在原生的UITableViewCell上添加UIButton按钮,然后给UIButton设置tag值,然后在控制器里的方法里通过取数据,做界面跳转等.还是举个例子吧,省的回忆半天. - (UI

  • IOS UITableView颜色设置的实例详解

    IOS UITableView颜色设置的实例详解 1.系统默认的颜色设置  //无色 cell.selectionStyle = UITableViewCellSelectionStyleNone; //蓝色 cell.selectionStyle = UITableViewCellSelectionStyleBlue; //灰色 cell.selectionStyle = UITableViewCellSelectionStyleGray; 2.自定义颜色和背景设置 改变UITableView

  • IOS UITableView和NavigationBar的常用设置详解

    IOS UITableView和NavigationBar的常用设置详解 TableView: 1.tableview常用基本设置 // 清除父类UIEdgeInsets self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0,0); //禁止滚动 self.tableView.scrollEnabled = NO; // tableview头部视图设置 self.tableView.tableHeaderView =一个UIView; //

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

    UITableView可以说是UIKit中最重要的一个组件,用来展示数据列表,还可以灵活使用进行页面的布局.UITableView的使用遵循MVC模式,数据模型(NSObject).视图(UIView)和控制器(UITableViewController)分离.UITableView继承自UIScrollView,可上下滑动,可以作为跟视图也可以作为子视图组件. reuseIdentifier顾名思义是一个复用标识符,是一个自定义的独一无二的字符串,用来唯一地标记某种重复样式的可复用UITabl

  • ios开发UITableViewCell图片加载优化详解

    目录 前言 图片自适应比例 XHWebImageAutoSize 仅加载当前屏幕的内容 预加载 前言 我们平时用UITableView用的很多,所以对列表的优化也是很关注的.很多时候,我们设置UIImageView,都是比例固定好宽高的,然后通过 scaleAspectFill 和 clipsToBounds 保持图片不变形,这样子做开发的效率是很高的,毕竟图片宽高我们都是固定好的了. 那如果产品要求图片按真正的比例展示出来呢?如果服务器有返回宽和高,那就好办了,那如果没有呢,我们应该怎么去做呢

  • flutter中的资源和图片加载示例详解

    目录 封面图 指定相应的资源 资源绑定 Asset bundling 资源变体 加载资源 加载文本资源 加载图片 加载依赖包中的图片 最后 封面图 下个季度的目标是把前端监控相关的内容梳理出来,梳理出来之后可能会在公司内部做个分享- Flutter应用程序既括代码也包括一些其他的资产,我们通常这些资产为资源. 有时候我会思考assets这个单词,在程序中到底应该翻译为资产呢?还是翻译为资源?按照习惯,我们这里还是称为资源好了- 这些资源是一些与应用程序捆绑在一起和并且部署应用时会用到的的文件,在

  • 如何在iOS中高效的加载图片详解

    目录 前言 图片的渲染流程 DataBuffer SD源码分析 ImageBuffer 占用内存大小 Xcode测试 如何减少图像占用内存 向下采样 SD源码分析解码过程 选择正确的图片渲染格式 渲染格式 如何正确的选择渲染格式 减少后备存储器的使用 减少或者不使用 draw(rect:) 方法 如何在列表中加载图片 线程爆炸 总结 前言 在iOS开发中,图片(UIImage)是我们在开发中,占用手机内存比较大的对象,如果在运行过程中,内存占用过大,对电池寿命会造成影响,如果超过了内存占用的最大

  • Android开发中Bitmap高效加载使用详解

    由于Android对单个应用所施加的内存限制,比如16MB,这导致加载Bitmap的时候很容易出现内存溢出,本文主要包含2个方面的内容分析Bitmap内存和Bitmap高效加载 一.占用内存 获取bitmap的内存,android提供的方法bitmap.getByteCount() 假如现在mipmap-xhdpi 目录下,有一个 200 * 200 像素的图片,运行加载它,看它输出的尺寸. Bitmap bitmap= BitmapFactory.decodeResource(getResou

  • iOS中的应用启动原理以及嵌套模型开发示例详解

    程序启动原理和UIApplication   一.UIApplication 1.简单介绍 (1)UIApplication对象是应用程序的象征,一个UIApplication对象就代表一个应用程序. (2)每一个应用都有自己的UIApplication对象,而且是单例的,如果试图在程序中新建一个UIApplication对象,那么将报错提示. (3)通过[UIApplicationsharedApplication]可以获得这个单例对象 (4) 一个iOS程序启动后创建的第一个对象就是UIAp

  • 举例讲解iOS中延迟加载和上拉刷新/下拉加载的实现

    lazy懒加载(延迟加载)UITableView 举个例子,当我们在用网易新闻App时,看着那么多的新闻,并不是所有的都是我们感兴趣的,有的时候我们只是很快的滑过,想要快速的略过不喜欢的内容,但是只要滑动经过了,图片就开始加载了,这样用户体验就不太好,而且浪费内存.              这个时候,我们就可以利用lazy加载技术,当界面滑动或者滑动减速的时候,都不进行图片加载,只有当用户不再滑动并且减速效果停止的时候,才进行加载.               刚开始我异步加载图片利用SDWe

  • iOS中的多线程如何按设定顺序去执行任务详解

    多线程概述 对于ios系统中的某个App来讲,是单进程多线程方式来工作.一般来说,使用多线程的好处是可以把程序分成相对独立的几个模块,可以有效的防止某个模块堵塞的时候导致整个程序卡死:还有就是提高运行效率,现在CPU都是多核,多个核可以同时跑,可以同时执行多条线程. 经常有这样的需求: 1,有m个网络请求. 2,先并发执行其中n几个. 3,待这n个请求完成之后再执行第n+1个请求. 4然后等 第n+1个请求完成后再并发执行剩下的m-(n+1)个请求. 如果我们用GCD,可以使用dispatcg_

  • Android开发中Activity的生命周期及加载模式详解

    本文给大家介绍Activity的生命周期,如果大家学习过iOS的小伙伴的话,Activity的生命周期和iOS中ViewController的生命周期非常类似.生命周期,并不难理解.一个人的生命周期莫过于生老病死,花儿的生命周期就是花开花谢了.在Android中Activity的生命周期莫过于Activity的创建到消亡的过程了.本篇博客就会介绍Activity生命周期中的不同阶段,通过实例的形式来窥探一下Activity的生命周期.搞明白Activity的生命周期是至关重要的,因为只有搞明白每

  • JavaWeb中web.xml初始化加载顺序详解

    需求说明 做项目时,为了省事,起初把初始化的配置都放在每个类中 static加载,初始化配置一多,就想把它给整理一下,这里使用servlet中的init方法初始化. web.xml说明 首先了解下web.xml中元素的加载顺序: 启动web项目后,web容器首先回去找web.xml文件,读取这个文件 容器会创建一个 ServletContext ( servlet 上下文),整个 web 项目的所有部分都将共享这个上下文 容器将 转换为键值对,并交给 servletContext 容器创建 中的

随机推荐