tableView入門到效能優化

高家二少爺發表於2018-07-13

標籤(空格分隔): UITableView UITableViewCell UITableView效能優化 UITableView效能優化精講


參考文章

摘要:UITableView是iOS開發中非常重要的控制元件之一,它能夠展示多行資料,支援滾動.在大部分APP中都佔有很大的比重.那麼有關UITableView的效能優化也顯得尤為重要,本文後面也將著重講這個。

##UITableView的簡單使用 ###1、UITableView的建立

程式碼方式

//tableView的建立必須制定一個樣式,樣式在初始化之後不能修改
//tableView分兩種風格,Plain:不分割槽,grouped:分割槽
_tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
    _tableView.delegate = self;
    _tableView.dataSource = self;
//告訴tableView,它上面的cell是根據UItableViewCell建立的,如果重用的cell找不到,系統會根據這個類來建立一個cell,不需要程式設計人員建立
//解釋2://給tableView的某個重用標示符註冊一個類,當tableView找這個重用標示符的cell,如果找不到,就會自動建立這個類的物件,(始終能找到可重用的cell)
    [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    [self.view addSubview:_tableView];
複製程式碼

xib方式 在設計介面拖入,並在屬性檢查器中設定各種屬性

###2、UITableView的屬性

// 設定每一行cell的高度
    self.tableView.rowHeight = 80;
// 設定每一組的頭部標題高度
    self.tableView.sectionHeaderHeight = 50;
// 設定每一組的尾部標題高度
    self.tableView.sectionFooterHeight = 50;
// 設定分割線的顏色
    self.tableView.separatorColor = [UIColor redColor];
// 設定分割線的樣式
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// 設定表頭控制元件---這裡主要應用是打廣告
    self.tableView.tableHeaderView = [[UISwitch alloc] init];
// 設定表尾控制元件---這裡主要應用是載入資料
    self.tableView.tableFooterView = [[UISwitch alloc] init];
// 設定索引條的文字顏色
    self.tableView.sectionIndexColor = [UIColor orangeColor];
// 設定索引條的背景顏色
    self.tableView.sectionIndexBackgroundColor = [UIColor yellowColor];
複製程式碼

###3、隱藏TableView分割線的方法

// 方法一:設定分割線的顏色
    self.tableView.separatorColor = [UIColor clearColor];
// 方法二:設定分割線的樣式
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
複製程式碼

###4、UITableViewCell的屬性

//cell中預設包含的空間
cell.imageView
cell.textLabel
cell.detaliTextLabel
cell.contentView
// 設定右邊顯示的指示控制元件
// accessoryView的優先順序 > accessoryType
cell.accessoryView = [[UISwitch alloc] init];

// 設定右邊顯示的指示樣式
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

// 設定cell選中的樣式
// 設定cell的選中樣式無
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// 下面三種方式在iOS7之後,表現形式一樣了,都是灰色
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
複製程式碼

###5、設定cell的背景顏色

// 總體效果為大背景藍色,字型背景為紅色,下面程式碼可以調換順序,效果一樣
cell.backgroundColor = [UIColor redColor];

UIView *bg = [[UIView alloc] init];
bg.backgroundColor = [UIColor blueColor];
cell.backgroundView = bg;
複製程式碼

###6、設定cell選中的背景View

// 設定cell選中的背景view
    UIView *seletedBg = [[UIView alloc] init];
    seletedBg.backgroundColor = [UIColor yellowColor];
    cell.selectedBackgroundView = seletedBg;
複製程式碼

###7、兩個協議

tablewView代理方法的執行順序。 UITableView返回多少組----->每組返回多少行cell--->計算每個cell的高度---->指定cell佈局

UITableViewDelegate

/**
 *  1.當使用者點選(選中)某一行cell就會呼叫這個方法
 */
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"選中了:%zd",indexPath.row);
}

/**
 *  2.當使用者取消點選(選中)某一行cell就會呼叫這個方法
 */
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"取消選中了:%zd",indexPath.row);
}

/**
 *  3.返回每一組的頭部高度
 */
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{

}

/**
 *  4.返回每一組的尾部高度
 */
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{

}

/**
 *  5.返回每一行cell的高度
 */
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row % 2 == 0) {
        return 50;
    } else {
        return 100;
    }
}

/**
 *  6.返回每一組的頭部控制元件
 */
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    return [UIButton buttonWithType:UIButtonTypeContactAdd];
}

/**
 *  7.返回每一組的尾部控制元件
 */

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    return [UIButton buttonWithType:UIButtonTypeContactAdd];
}
/*
* 8.設定每一行的編輯樣式:當表處於編輯狀態時,預設的編輯樣式為刪除
*/
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView 		editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
		{
			 //編輯狀態為刪除
   		 return UITableViewCellEditingStyleDelete;
   		 //編輯狀態為插入
    		 //return UITableViewCellEditingStyleInsert;
		}
		
//通過edditing的值來顯示編輯狀態
_tableView.editing = !_tableView.editing;


/*
*  9.表提交編輯狀態的時候會呼叫這個方法
*/
-(void)tableView:(UITableView *)tableView commitEditingStyle:	(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath 	*)indexPath
	{
	    [_dataArray removeObjectAtIndex:indexPath.row];
	    [_tableView reloadData];
	}
/*
*  10.設定編輯中刪除的文字
*/
-(NSString *)tableView:(UITableView *)tableView 	titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath 	*)indexPath
{
	return @"走你";
}
/*
*  11.右側按鈕被點選時會呼叫該方法
*/
	-(void)tableView:(UITableView *)tableView 	accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
	{
  		 NSLog(@"呼叫了cell右側按鈕的方法");
	}



複製程式碼

###8、兩個協議二:UITableViewDataSources



/**
 *  告訴tableView一共有多少組資料
 */
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView


/**
 *  告訴tableView第section組有多少行
 */
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

/**
 *  告訴tableView每一行顯示的內容(tableView每一行的內容是是第一個UITableViewCell)
 */
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

/**
 *  告訴tableView每一組的頭部標題
 */
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section

/**
 *  告訴tableView每一組的尾部標題
 */
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section

/**
 *  返回索引條的文字
 */
- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
//    NSMutableArray *titles = [NSMutableArray array];
//    for (XMGCarGroup *group in self.carGoups) {
//        [titles addObject:group.title];
//    }
//    return titles;

// 抽取self.carGoups這個陣列中每一個元素(XMGCarGroup物件)的title屬性的值,放在一個新的陣列中返回
    return [self.carGoups valueForKeyPath:@"title"];
複製程式碼

##二、UITableViewCell 在UITableView中,每一個單元格被稱為cell,如果想在UITableView中顯示資料,需要設定UITableView中cell的數量及每個cell顯示的內容.UITableView並不能直接顯示資料,它需要設定資料來源(datasource),資料來源遵守協議,並實現其中對應的方法設定資料及內容即可. ###UITableViewCell的建立

  1. 設定Cell的三個屬性

    cell.imageView     //cell左邊的圖片
    cell.textLabel		  
    cell.detailTextLabel
    複製程式碼
  2. 通過自定義類 建立一個累繼承於UITableViewCell,新增如下屬性

    @property (nonatomic,retain)UIImageView *headerImageView;
    @property (nonatomic,retain)UILabel *nameLabel;
    @property (nonatomic,retain)UILabel *timeLabel;
    @property (nonatomic,retain)UILabel *messageLabel;
    @property (nonatomic,retain)UIImageView *picImageView;
    複製程式碼

    .m檔案中的實現

    //如果是alloc建立的cell,各個自定義屬性空間的初始化程式碼寫在init方法中
    (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString 	*)reuseIdentifier{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
            //頭像
        _headerImageView = [[UIImageView alloc] initWithFrame:CGRectMake(8, 8, 60, 60)];
        _headerImageView.layer.cornerRadius = 30;
        _headerImageView.layer.masksToBounds = YES;
        [self.contentView addSubview:_headerImageView];       
        //名字
        _nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(76, 8, 240, 20)];
        _nameLabel.font = [UIFont boldSystemFontOfSize:16];
        _nameLabel.textColor = [UIColor redColor];
        [self.contentView addSubview:_nameLabel];   
        //釋出時間
        _timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(76, 36, 240, 20)];
        _timeLabel.font = [UIFont systemFontOfSize:12];
        [self.contentView addSubview:_timeLabel];        
        //動態正文
        _messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 76, 304, 60)];
        _messageLabel.font = [UIFont systemFontOfSize:16];       
        //numberOfLines行數,設定為0不限制行數。
        _messageLabel.numberOfLines = 0;        
        [self.contentView addSubview:_messageLabel];        
        //動態配圖
        _picImageView = [[UIImageView alloc] initWithFrame:CGRectMake(8, 144, 100,100)];
        [self.contentView addSubview:_picImageView];
        }
        return self;
        }	
    複製程式碼
  3. 通過xib自定義 首先建立一個帶xib的UITableViewCell,拖控制元件設定cell高度。拉線生成屬性;

     自定義類中
     //如果cell是從xib中載入的,那麼不走init方法,所以初始化程式碼不能寫在init方法中
		//當自身從xib中載入的時候呼叫,所以自定義xib的程式碼應該在這裡實現
		(void)awakeFromNib {
    		[super awakeFromNib];
   	 // Initialization code
    		_headerImageView.layer.cornerRadius = 30;
   	 _headerImageView.layer.masksToBounds = YES;
		}
複製程式碼

註冊重用

```
//給TableView的某個重用標示符註冊一個xib,當tableView找這個識別符號時,如果找不		    到,就會自動從註冊的西邊中區載入一個cell。
[_tableView registerNib:[UINib nibWithNibName:@"CustomCell" 		bundle:nil]     forCellReuseIdentifier:@"cell"];
```
複製程式碼

##三、效能優化

1)儘量使用cell的複用。

使用cell的複用,可以減少記憶體的開銷,沒有開闢新的空間,也減少了一些計算量。

複用原理:當滾動列表時(UITableView)部分cell會移除Window 但是移除的cell並沒有被立即釋放 而是放到了一個叫做複用池的物件池中,處於待定狀態,當有新的cell要出現在Window介面上時,首先會從複用池中尋找是否有相同型別的cell,如果有直接拿過用(最直觀的表現是新出現的cell有沒有開闢新的記憶體空間),如果沒有,建立一個新的型別的cell,所以UITableView可能擁有多種型別的cell,複用池也可能儲存著多種型別的cell,系統通過定義reuseIndentifer作為每個cell的唯一標示符來確定即將出現的cell複用何種型別的cell。

2)對於不定高的cell 提前將每個cell的高度存入陣列,出現一個cell的時候,直接從陣列中拿出確切的高度即可,不用臨時計算cell的高度。對於固定高的cell和不定高的cell同樣適用。同樣也可以在儲存在model中,在獲取資料後要賦值給model時進行計算。

3)涉及網路請求載入資料在UITableView滑動結束的時候在進行載入資料(渲染)避免卡頓。

1、UITableView繼承自UIScrollView,繼承了後者的方法。
//滑動結束的方法
- (void)scrollViewDidEndDragging:(UIScrollView*)scrollView willDecelerate:(BOOL)decelerate
//減速結束之後的方法
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView

2、在tableView必須實現的二個方法中(載入cell的方法中)將資料的渲染寫在以下if語句中
if(self.tableView.dragging==NO&&self.tableView.decelerating==NO)
複製程式碼

4)對於tableView的自定義控制元件 尤其是UIImageView,儘量減少使用圓角,陰影等layer屬性,儘量減少使用alpha(透明度)來設定透明度,(在專案開發中,讓UI設計師設計原圖就是帶圓角的圖) 陰影,圓角這些layer效果都是在GPU中完成的。

1、當多個檢視重疊時,一個畫素同時屬於很多subviews,GPU會進行合成渲染,需要渲染一個畫素兩次或多次,而渲染的最慢的操作就是混合。因此當檢視結構太過複雜,就會大量消耗GPU的資源。所以當一個空間本身是不透明,注意設定alpha為1,這樣可以避免無用的alpha通道合成,降低GPU的負載。 另外在layer層渲染圖層時會涉及上下文切換以及離屏渲染之類的,系統開銷會很大,特別是在cell檢視很複雜的時候,由於渲染問題導致的記憶體開銷會讓你的tableview非常卡頓。比如cell中需要設定頭像圓形直接設定圓角會很卡,那麼我們可以用Quartz2d把拿到的圖片處理一遍在給cell使用。

2、對控制元件設定cornerRadius後對其進行clip或mask操作時 會導致offscreenrendering這個也是在GPU中進行的 如果在滑動時 圓角物件太多 回到GPU的負載大增幅。

這時我們可以設定layer的shouldRasterize屬性為YES,可以將負載轉移給CPU,更徹底的是直接使用帶圓角的原圖。

5)儘量使用懶載入

懶載入又稱為延遲載入,實際上是重寫某個物件的getter方法 原理:程式一開始並不對它進行初始化 而是在用到他的時候 才為他開闢記憶體供它使用。

好處:

1、不必將建立的物件的程式碼全部寫在ViewDidLoad中,程式碼可讀性強。 2、每個控制元件的getter方法,分別負責各自的例項化處理,程式碼彼此之間獨立性強 鬆耦合。

6)減少返回給的cell裡面的處理邏輯和處理時間。

以驢媽媽為例:各個UI控制元件整合到一起,實際上只有一個控制元件。

7)設定每個cell的opaque屬性 ----面試亮點

opaque意思是不透明的 渾濁的 有YES和NO二個結果,如果控制元件本身不透明,我們設定opaque為YES。

opaque為YES表示告訴iOS當前檢視背後沒有需要繪製的內容,同時允許iOS的繪圖方法通過一些優化來加速當前檢視的繪製。

為什麼我們設定Alpha的值為1的時候仍然要設定opaque的屬性為YES? Alpha屬性只對當前需要繪製的檢視起作用,如果當前檢視並沒有填滿父檢視或者當前檢視上存在一些洞(由Alpha通道所致),那麼影象檢視下方的內容將仍然可見,不管Alpha的值是多少。選中就是讓iOS明白不需要為影象檢視之後的東西浪費繪製時間。

以下是官方描述

default is YES. opaque views must fill their entire bounds or the results are undefined. the active CGContext in drawRect: will not have been cleared and may have non-zeroed pixels

8)分段載入資料

設定分頁載入資料,也就是上拉重新整理和下拉載入。

以下是cell簡單複用程式碼

#import "ViewController.h"
#import "XMGTestCell.h"

@interface ViewController ()

@end

@implementation ViewController

NSString *ID = @"wine";
- (void)viewDidLoad {
    [super viewDidLoad];

    self.tableView.rowHeight = 100;

    // 註冊 ID這個標識 對應的 cell型別 為UITableViewCell這種型別
    [self.tableView registerClass:[XMGTestCell class] forCellReuseIdentifier:ID];
}

#pragma mark - 資料來源方法

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 200;
}

/**
 *  每當一個cell進入視野範圍內就會呼叫1次
 */
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 1.去快取池取可迴圈利用的cell
    XMGTestCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    // 2.設定資料
    cell.textLabel.text = [NSString stringWithFormat:@"第%zd行資料",indexPath.row];
    return cell;

}
複製程式碼

相關文章