簡介:
UITableView我想大家都不陌生,他是UIKit一個重要組件。可以用來展示數(shù)據(jù)列表,或者靈活使用進行頁面布局。
其使用中遵循MVC模式,數(shù)據(jù)模型(NSObject)、視圖(UIView)、控制器(UITableViewController)分離。 點擊前往Github下載Demo
官方文檔使用:
// 注冊方式
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"identifier"] ;
//返回每一組的每一行顯示什么內(nèi)容
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// 定義一個重用標示,用static修飾。就放在了內(nèi)存的靜態(tài)區(qū)了。
static NSString *identifier = @"Cell";
// 緩存池中尋找是否有可以重用的cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
// 如果緩存池中沒有ID,創(chuàng)建一個cell,并給它一個重用標示
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
// 設置數(shù)據(jù),賦值給cell
return cell;
}
為了做到顯示和數(shù)據(jù)分離,iOS TableView的實現(xiàn)并不是為每一個數(shù)據(jù)創(chuàng)建cell,而是創(chuàng)建屏幕可顯示最大個數(shù)+1的cell,對cell做單獨顯示配置,來達到既不影響顯示效果,又能充分節(jié)約內(nèi)容的目的。
重用機制原理:

機制
假設虛線范圍是屏幕的顯示區(qū)域,整個屏幕里面每個Cell的identifier是一樣的。
A2、A6 的Cell有一部分是在屏幕內(nèi)。
A3、A4、A5的Cell全部在屏幕內(nèi)。
系統(tǒng)會創(chuàng)建當前屏幕Cell個數(shù)+1的Cell,A1在屏幕外,現(xiàn)在它就被放到了重用池;
向上滑動時候,新的CellA7就會去重用池里面根據(jù)指定indentifier取出A1存放的Cell。
就如同盤子使用了之后,洗完可以繼續(xù)使用。
作用:
避免大量創(chuàng)建實例對象,減少
Memory Warning內(nèi)存的消耗甚至Crash掉,從而提高滑動流暢性,提高用戶體驗!
UITableView的性能優(yōu)化
一般開發(fā)過程中遇到的幾個重要的問題:
cellForRowAtIndexPath:方法中處理了過多的業(yè)務。
cell的height動態(tài)變化時計算方式不對。
tableviewCell的subview層級太復雜,做了大量透明處理。
1、cell的數(shù)據(jù)綁定
在使用的過程中我們注意到cellForRowAtIndexPath:中為每一個cell綁定數(shù)據(jù),在實際調(diào)用中cellForRowAtIndexPath:時候cell還沒有被顯示出來,為了提高效率應該把綁定數(shù)據(jù)操作放在cell顯示后再執(zhí)行,可以在tableView:willDisplayCell:forRowAtIndexPath:方法中綁定數(shù)據(jù)。
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
// model類在setter方法中進行設置也可。
cell.textLabel.text = @"這里進行數(shù)據(jù)綁定";
}
2、cell高度計算
cell的高度分為兩種,一種是常用的固定高度,一種類似微博首頁的動態(tài)計算高度。
(1)固定高度Cell
self.tableView.rowHeight = 55;
這個方法指定了所有cell高度都是55,rowHeight默認的值是44。對于定高cell,直接采用上面方式給定高度,不需要實現(xiàn)tableView:heightForRowAtIndexPath: 以節(jié)省不必要的計算和開銷【重要】。
(2)動態(tài)計算高度Cell
動態(tài)計算高度,我們需要用到heightForRowAtIndexPath方法,這個代理方法實現(xiàn)之后,rowHeight設置將會變成無效。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 55 ;
}
思考:
類似微博項目根據(jù)傳入那些動態(tài)的元素(文字,圖片等),然后返回計算后的高度。這樣沒有問題,只是計算量很復雜,每次reloadData,【UITableView在每次reloadData的時候都要刷新所有cell高度,如果你有100行cell,代理就會執(zhí)行100次cell高度,而不是屏幕顯示cell的數(shù)量的高度。】很消耗性能。
方法1:
iOS8之后,有了self-sizing cell的概念,cell可以自己算出高度,不過使用起來會有三個條件:
- 使用Autolayout進行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關系)。
- 指定TableView的estimatedRowHeight屬性的默認范圍值。
- 指定TableView的rowHeight屬性為UITableViewAutomaticDimension。
self.tableView.estimatedRowHeight = 55.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
方法2:
賦值和計算布局分離,在服務器異步獲得數(shù)據(jù)之后,根據(jù)數(shù)據(jù)源計算出對應的布局,并緩存到數(shù)據(jù)源中。這樣在tableView:heightForRowAtIndexPath:方法中就直接返回高度,而不需要每次都計算了。
新建一個dataModel類,新建
@property (nonatomic, assign) CGFloat cellViewH;
- (CGFloat)cellViewH {
// 寫入你的業(yè)務邏輯根據(jù)不同內(nèi)容變化高度
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
dataModel *model = self.[indexPath.row];
return model.cellViewH;
}
這樣的方法基本能滿足簡單界面,但是針對朋友圈圖文混排,這樣還是需要繼續(xù)優(yōu)化的。
方法3:
TODO
3、頁面
方法1、圓角優(yōu)化
我們一般設置圓角的方式如下:
self.imageView.layer.cornerRadius=CGFloat(10);
self.imageView.layer.masksToBounds=YES;
這種處理的渲染機制是GPU在當前屏幕緩沖區(qū)外新開辟一個渲染緩沖區(qū)進行工作,也就是離屏渲染。如果圓角操作達到一定數(shù)量,會觸發(fā)緩沖區(qū)的頻繁合并和上下文頻繁切換,性能的代價會宏觀地表現(xiàn)在用戶體驗上——掉幀。
方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"myImg"];
//開始對imageView進行畫圖
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
//使用貝塞爾曲線畫出一個圓形圖
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
方案2:使用CAShapeLayer和UIBezierPath設置圓角
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image=[UIImage imageNamed:@"myImg"];
UIBezierPath *maskPath=[UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer=[[CAShapeLayer alloc]init];
maskLayer.frame=imageView.bounds;
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.view addSubview:imageView];
方法2、滑動時按需加載對應的內(nèi)容
如果目標行與當前行相差超過指定行數(shù),只在目標滾動范圍的前后指定3行加載。
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollViewwithVelocity(CGPoint)velocitytargetContentOffset(inoutCGPoint *)targetContentOffset{
// 代碼待整理
}
方法3、定義一種(盡量少)類型的Cell,擅長使用hidden隱藏(顯示)
分析cell結構,盡可能將相同內(nèi)容抽取到一種樣式的cell,這樣雖然cell的體積會大很多,但是數(shù)量不會多。這樣的好處:
* 減少代碼量,減少XIB文件的數(shù)量,容易修改和維護。
* 基于cell重用,運行時鋪滿屏幕所需cell數(shù)量固定N個,如果只有一種cell,那就是N個cell實例,如果M中cell,可能會是MN個cell實例,相比之下占用更多內(nèi)存。
方法4、使用不透明視圖
不透明的視圖可以極大地提高渲染的速度。因此如非必要,可以將table cell及其子視圖的opaque屬性設為YES(默認值UIButton內(nèi)部的label的opaque默認值都是NO])。
Cell中不要使用clearColor,無背景色,透明度也不要設置為0。
方法5、使用局部更新
如果只是更新某組的話,使用reloadSection進行局部更新
方法6、不要給cell動態(tài)添加subView
在初始化cell的時候就將所有需要展示的添加完畢,然后根據(jù)需要來設置hide屬性顯示和隱藏。
方法7、異步化UI,不要阻塞主線程
我們時常會看到這樣一個現(xiàn)象,就是加載時整個頁面卡住不動,怎么點都沒用,仿佛死機了一般。原因是主線程被阻塞了。所以對于網(wǎng)路數(shù)據(jù)的請求或者圖片的加載,我們可以開啟多線程,將耗時操作放到子線程中進行,異步化操作。