你的位置:首页 > 操作系统

[操作系统]你真的了解UITableViewCell重用吗?


一:首先查看一下关于UITableViewCell重用的定义

- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; - (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);

在tableview新建的时候,会新建一个复用池(reuse pool).这个复用池可能是一个队列,或者是一个链表,保存着当前的Cell.pool中的对象的复用标识符就是reuseIdentifier,标识着不同的种类的cell.所以调用dequeueReusableCellWithIdentifier:方法获取cell.从pool中取出来的cell都是tableview展示的原型.无论之前有什么状态,全部都要设置一遍.

在UITableView创建同时,会创建一个空的复用池.之后UITableView在内部维护这个复用池.一般情况下,有两种用法,一种是在取出一个空的cell的时候再新建一个.一种是预先注册cell.之后再直接从复用池取出来用,不需要初始化.

第一种方法:

- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; 

UITableview第一次执行tableView:cellForRowAtIndexPath:.当前复用池为空.dequeueReusableCellWithIdentifier调用中取出cell,并检测cell是否存在.目前并不存在任何cell,于是新建一个cell,执行初始化, 并return cell.

代码如下:

#define kInputCellReuseIdentifierPWD  @"password_cell"UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kInputCellReuseIdentifierPWD];if (!cell) {  cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kInputCellReuseIdentifierPWD];}cell.textLabel.text = @"It is awesome";return cell;

上面的代码中,你返回的cell会被UITableView添加到复用池中.第二次调用tableView:cellForRowAtIndexPath:,当前复用池中有一个cell.这时候因为UITableView上面还未填满,而且复用池中唯一的那一个已经在使用了.所以取出来的Cell仍然是nil.于是继续新建一个cell并返回,复用池再添加一个cell,当前复用池中cell的个数为2.假如当前tableview只能容纳5个cell.那么在滚动到第6个cell时,从tableview的复用池取出来的cell将会是第0行的那个cell.以此类推,当滚动到第7行时,会从复用池取出来第1行的那个cell. 另外,此时不再继续往复用池添加新的cell.

第二种方法:

- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);

在UITableView初始化的时候,注册一个UITableViewCell的类;不用再做空判断;

[tableView registerClass:[BLSProjectMoneyCompleteCell class] forCellWithReuseIdentifier:NSStringFromClass([BLSProjectMoneyCompleteCell class])];

使用此方法之后,就不用再判断取出来的cell是否为空,因为取出来的cell必定存在.调用dequeueReusableCellWithIdentifier:方法时,会先判断当前复用池时候有可用复用cell.如果没有,tableview会在内部帮我们新建一个cell,其他的跟方法一一样

BLSProjectMoneyCompleteCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([BLSProjectMoneyCompleteCell class])];

二:UITableViewCell重用时引发的问题

UITableView中的cell可以有很多,一般会通过重用cell来达到节省内存的目的:通过为每个cell指定一个重用标识符(reuseIdentifier),即指定了单元格的种类,当cell滚出屏幕时,会将滚出屏幕的单元格放入重用的queue中,当某个未在屏幕上的单元格要显示的时候,就从这个queue中取出单元格进行重用。

但对于多变的自定义cell,有时这种重用机制会出错。比如,当一个cell含有一个UITextField的子类并被放在重用queue中以待重用,这时如果一个未包含任何子视图的cell要显示在屏幕上,就会取出并使用这个重用的cell显示在无任何子视图的cell中,这时候就会出错。

方法一:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {   static NSString *CellIdentifier = @"Cell";   // UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; //改为以下的方法   UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; //根据indexPath准确地取出一行,而不是从cell重用队列中取出   if (cell == nil) {     cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];   }   //...其他代码                } 

将获得cell的方法从- (UITableViewCell*)dequeueReusableCellWithIdentifier:(NSString*)identifier 换为-(UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;重用机制调用的就是dequeueReusableCellWithIdentifier这个方法,方法的意思就是“出列可重用的cell”,因而只要将它换为cellForRowAtIndexPath(只从要更新的cell的那一行取出cell),就可以不使用重用机制,因而问题就可以得到解决,虽然可能会浪费一些空间。

方法二:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {   NSString *CellIdentifier = [NSString stringWithFormat:@"Cell%d%d", [indexPath section], [indexPath row]];//以indexPath来唯一确定cell   UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; //出列可重用的cell   if (cell == nil) {     cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];   }   //...其他代码 } 

通过为每个cell指定不同的重用标识符(reuseIdentifier)来解决。重用机制是根据相同的标识符来重用cell的,标识符不同的cell不能彼此重用。于是我们将每个cell的标识符都设置为不同,就可以避免不同cell重用的问题了。

方法三:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {   static NSString *CellIdentifier = @"Cell";   UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; //出列可重用的cell   if (cell == nil) {     cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];   }   else   {     //删除cell的所有子视图     while ([cell.contentView.subviews lastObject] != nil)     {       [(UIView*)[cell.contentView.subviews lastObject] removeFromSuperview];     }   }   //...其他代码 }

删除重用cell的所有子视图;这个方法是通过删除重用的cell的所有子视图,从而得到一个没有特殊格式的cell,供其他cell重用。考虑到内存问题,cell少得时候可以每个都添加标识符,当cell重用较多时,考虑内存问题,建议用删除cell的所有子视图方法(做视频播放的时候).

三: 有时Assertion failure in dequeueReusableCellWithIdentifier:forIndexPath: 闪退问题

在先前做的分组列表中,两组的Cell是不一样样式,两组分别运用如下的代码(也有在初始化注册Cell),在IOS9以下会闪退

static NSString *CellIdentifier = @"Cell";UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier                   forIndexPath:indexPath];

后来修改成:

static NSString *CellIdentifier = @"Cell";  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];  if (cell==nil) {   cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];  }