通过UINib加速table视图的cell加载

    我感觉在Apple发布iOS 4.0后很久仍可以发现自己错失了该版本中的一些东西。该版本引入了名为UINib一个新类,该类将Mac OS X中长期使用的NSNib类的一些功能引入到了iOS中。

    iOS上UINib(以及Mac OS X上的NSNib)的作用就是加速频繁使用的NIB文件的加载。在第一次从硬盘加载NIB时,它在内存中缓存NIB文件对象。之后加载NIB文件时就会从内存拷贝而避免了较慢的硬盘访问。Apple宣称可以在加载NIB文件时提供2倍的速度提升。

    使用UINib的最明显的地方就是在需要在每次创建新Cell时从NIB文件中加载Cell的UITableViewControllers中。UINib的优势就是在不用大量修改代码的情况获得性能改进。为了展示,首先我们会回顾下从NIB加载表格视图的Cell的标准机制,然后介绍如何修改代码以利用UINib。

加载在NIB文件中的table视图cell

    Table View Programming Guide详细描述了从Nib文件加载一个定制表格视图的cell的技术。这里我会跳过一些细节,最基本的要求就是创建一个包含着单个用于填充表格的UITableViewCell的Nib文件。利用XCode模板创建一个包含两个Tab页面的示例应用。在第一个视图控制器使用传统的Nib加载技术,而在第二个中使用UINib,这样就可以轻松进行两者之间的比较。

    为此我们创建一个如下所示的包含三个Label子视图的表格视图的Cell。

      这样Cell就包含在一个名为LabelCell.xib的Nib文件中,在Interface Builder中将UITableViewCell的标识符设成“LabelCell” ,并为各个UILabel视图设定一个唯一的tag,这样就可以在之后获取到(具体步骤可以参照Table View Programming Guide)。

    第一个table视图控制器没有什么特殊说明的。最有意思的为表格的每行创建cell的方法的代码如下:

 

  
  
  
  
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    
  2.     static NSString *CellIdentifier = @"LabelCell";       
  3.     NSUInteger row = [indexPath row];    
  4.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];    
  5.     if (cell == nil) {      
  6.         [[NSBundle mainBundle] loadNibNamed:@"LabelCell" owner:self options:nil];      
  7.         cell = self.labelCell;      
  8.         self.labelCell = nil;    
  9.     }     
  10.     UILabel *label1 = (UILabel *)[cell viewWithTag:TCTAG_LABEL1];    
  11.     label1.text = [NSString stringWithFormat:@"Item %d", row+1];    
  12.     UILabel *label2 = (UILabel *)[cell viewWithTag:TCTAG_LABEL2];    
  13.     label2.text = [NSString stringWithFormat:@"Item %d", row+1];    
  14.     UILabel *label3 = (UILabel *)[cell viewWithTag:TCTAG_LABEL3];    
  15.    label3.text = [NSString stringWithFormat:@"Item %d", row+1];     
  16.     return cell;  

   这是一个表格视图控制器中很标准的实现。如果队列中没有可以复用的cell,我们就可以从LabelCell Nib文件中加载并创建一个新的cell。在IB中将labelCell的outlet连接到了UITableViewCell ,这样加载Nib文件后,它就会被设成新创建的对象。在保存要方法的返回值后将outlet设成nil。子视图可以通过在IB中设定的tag进行查找。

运行后用户界面如下:

    在视图首次加载时会调用cellForRowAtIndexPath:indexPath方法创建新的UITableViewCell。每次创建一个新的cell时,会从硬盘读取LabelCell nib文件。考虑到竖版iPhone屏幕的尺寸,可能需要创建10个cell,因此就要读取10次Nib文件。需要注意的是,在滚动table视图时,在顶部或者底部的cell会消失并被重用。这就意味着最多还会在创建一两个cell。

使用UINib

    第二个表格视图控制器使用了UINib优化来一次加载LabelCell Nib,并在需要创建新的Cell对象时使用内存中的缓存。为了保存一个Cell对象的缓存拷贝,我们在表格视图控制器中需要一个对象变量。该对象变量的属性如下所示:

  
  
  
  
  1. @property (nonatomic, retain) id labelCellNib; 

    该对象变量有获取/设定方法(我们仅重写获取方法),表格视图控制器中合成这些方法的代码如下:

  
  
  
  
  1. @synthesize labelCellNib=_labelCellNib

    由于在属性声明中指定了retain,因此需要在dealloc方法中释放该对象:

  
  
  
  
  1. - (void)dealloc {   
  2.     [_labelCellNib release];   
  3.     [super dealloc];   
  4. }  

    注意该属性的类型是“id”,这是因为UINib类仅在iOS 4.0及其之后的版本中可用。这就意味着如果我要创建一个在3.x版本的iOS上也能运行的应用就需要检查该类的存在。不幸的是为了确保向前兼容所要进行的运行时检查比通常要复杂。

警告:

iOS 4.0的发布文档包含如下的警告:

iOS 4包含一个支持从nib文件快速解档的UINib类。虽然该类是iOS SDK 4的新类,在之前的发布版本中该类是以私有类存在的。在部署使用UINib类同时需要在版本4之前的iOS中运行的代码时应该特别小心。具体来说,你无法通过NSClassFromString函数来确定该类是否可用。你还必须使用respondsToSelector:方法来查看其是否响应nibWithNibName:bundle:方法。如果可以响应该方法就可以使用该类。

    因此仅通过NSClassFromString函数检查UINib类是否存在是不够的,因为该类在之前版本中也是存在的。在检查类是否存在的同时还需要检查其是否响应nibWithNibName:bundle:方法。为此,我们定义了一个包括运行时检查的labelCellNib 实例变量的获取方法:

  
  
  
  
  1. - (id)labelCellNib {   
  2.     if (!_labelCellNib) {   
  3.         Class cls = NSClassFromString(@"UINib");   
  4.         if ([cls respondsToSelector:@selector(nibWithNibName:bundle:)]) {   
  5.             _labelCellNib = [[cls nibWithNibName:@"LabelCell" bundle:[NSBundle mainBundle]] retain];   
  6.         }   
  7.     }   
  8.     return _labelCellNib;   
  9. }  

    因此在第一次试图访问labelCellNib属性的时候,我们需要调用获取方法,在该方法中如果该成员变量是空就进行所需的运行时检查。只有在确认存在合法的UILib对象的情况下才使用nibWithNibName:bundle:方法来从硬盘中加载Nib文件并将其保存在成员变量中。注意我们需要保留加载的Nib文件因为我们希望它在视图控制器存在期间存在。之后的访问会直接返回保存的值。

    最后一件需要做的就是定义释放缓存的Nib对象的viewDidUnload方法:

 

  
  
  
  
  1. - (void)viewDidUnload {   
  2.     [super viewDidUnload];   
  3.     self.labelCellNib = nil;  
  4. }  

    为了使用缓存的UITableViewCell,必须对如下的cellForRowAtIndexPath方法稍微进行些改动(为了简洁略去了一些重复代码):

  
  
  
  
  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {   
  2.     static NSString *CellIdentifier = @"LabelCell";       
  3.     NSUInteger row = [indexPath row];   
  4.     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];   
  5.     if (cell == nil) {   
  6.         if ([self labelCellNib]) {   
  7.             [[self labelCellNib] instantiateWithOwner:self options:nil];   
  8.         } else {   
  9.             [[NSBundle mainBundle] loadNibNamed:@"LabelCell" owner:self options:nil];   
  10.         }   
  11.         cell = self.labelCell;   
  12.         self.labelCell = nil;   
  13.     }   
  14.     ...   
  15.     return cell;   
  16. }  

    这次在创建一个新的UITableViewCell时,我们会先检查labelCellNib的成员变量是否被设定。在iOS 4.0以及之后的系统中,通过之前的getter方法,会在第一次访问它时读取硬盘中的Nib文件。所有之后的访问都会返回Nib对象的缓存版本。instantiateWithOwner:options:用于创建一个Nib的可用拷贝,而避免从硬盘读取。如果运行在iOS 3.x,代码就会回滚到原来的Nib加载代码。

是否值得?

    在本例中使用UINib类意味着可以在每次加载视图时避免10次的Label Nib文件加载。这使得视图在加载时看起来快了,但是我必须说的是在真机测试上几乎很难看出速度改进。

    由于实际的速度改进是很有限的因此有必要问下是否值得这么做。老实说我也不是很确信,尤其是现代的iOS设备比之前的设备在性能方面有了很大的改进。但是,如果你看过两个表格视图控制器后,你会发现额外的工作相当少,因此我感觉在从Nib文件中加载定制Cell时这是值得的。

    在这里你可以找到本文中所用的Xcode项目。

 

原文链接:http://useyourloaf.com/blog/2011/2/28/speeding-up-table-view-cell-loading-with-uinib.html

你可能感兴趣的:(职场,休闲,UINib,定制Cell)