之前一個月剛剛系統的開始接觸IOS開發,對UI控制元件大體瞭解了一遍,但是因為沒有實際的參與專案,對細枝末節的還是不很清楚。
昨天突然想到:UITableViewCell的重用到底是怎麼回事,上網查了許多資料後略有體會,但大都差不多,於是想自己實驗一下。
便新建了個single view的工程,在storyboard上拖了個tableview,用最基礎的方法繫結了cell,並用了重用。
1 -(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 2 { 3 //為表格行定義一個靜態字串作為標示 4 static NSString *cellID = @"cellId"; 5 NSLog(@"hanghao:%ld",(long)indexPath.row); 6 //從可重用的表格行的佇列中取出一個表格行 7 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; 8 if (cell == nil) { 9 NSLog(@"重新建立物件"); 10 switch (indexPath.row % 4) { 11 case 0: 12 //使用UITableViewCell建立普通單元格,使用自定義的LYCTableViewCell建立自定義單元格 13 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID]; 14 break; 15 case 1: 16 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; 17 break; 18 case 2: 19 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellID]; 20 break; 21 case 3: 22 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:cellID]; 23 break; 24 default: 25 break; 26 } 27 } 28 //單元格設為圓角 29 cell.layer.cornerRadius = 12; 30 cell.layer.masksToBounds = YES; 31 32 //獲取當前的行號 33 NSUInteger rowNo = indexPath.row; 34 cell.textLabel.text = [books objectAtIndex:rowNo]; 35 //為uitableviewcell左端設定圖片 36 cell.imageView.image = [UIImage imageNamed:@"123.png"]; 37 //設定左端高亮圖片 38 cell.imageView.highlightedImage = [UIImage imageNamed:@"123_h.png"]; 39 40 cell.detailTextLabel.text = [details objectAtIndex:rowNo]; 41 return cell; 42 }
說實話,剛開始接觸的時候真不知道 dequeueReusableCellWithIdentifier 這是個什麼東西,後來在網上搜尋的時候說這是根據id查詢可重用的cell,但是說的太籠統了,到底什麼算是可重用cell?
如果這個table中有多個自定義型別的cell,該怎麼重用,這個問題糾結了一下午,到晚上睡覺前終於想通了。
先說簡單的情況,當我們的tableview種的cell為單一型別,而且cell的高度是一個統一的高度,即顯示一個這樣的列表:
例如上圖,那麼在 -(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
方法中,if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cID];
}
這幾行程式碼將被執行11次,也就是建立11個指向不同地址的cell。
為什麼是11次?因為當前window中顯示了11個cell,雖然最後一個只顯示了一半。(如果在iphone 6 plus中,建立的cell肯定大於11個,因為螢幕比5s的大)
這是在靜止時候建立的cell。
那麼當我們滑動螢幕的時候,有會發生或者觸發哪些操作呢?
上面的圖中,為將tableview向上滑動了一點,出現了一個新的cell(android高階),這個時候,系統會在建立一個新的cell物件,此時的記憶體中會有12個cell的物件。
當我們繼續向上滑動,當[C#高階程式設計]對應的那個cell消失在tableview的可視區域的時候,那個cell將被tableview放到他的重用列表中。
而新出現的cell將會重用重用列表中的那個cell,只是對cell中的文字重新賦值而已,
這樣一直迴圈:當使用者滾動tableview時,如果cell不可見,將被扔進可重用列表,在其他行即將顯示在tableview中時,重用那個cell,重新複製,以達到節省記憶體的效果。
按照上面的例子,那麼記憶體中最多會建立12個cell,即使你的datasource中有1000條資料,也還是12個cell物件在記憶體中,只是tableview替我們控制了顯示隱藏時重用cell。
如果我們不在程式設計時使用重用機制,那麼可想而知,建立1000個甚至10000個cell在記憶體中,是多麼浪費。
上面講了單一種類cell的現實,那麼多種cell是怎麼顯示的呢,其實只要上面的思路清晰了,那麼多種cell也是同樣的道理。
先上一下原始碼
1 #pragma mark - Table view data source 2 3 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 4 // Return the number of sections. 5 return 4; 6 } 7 8 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 9 // Return the number of rows in the section. 10 NSLog(@"rowNumber"); 11 if (section % 2 == 0) { 12 return 1; 13 } 14 return 30; 15 } 16 17 18 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 19 static NSString *defaultCellID = @"default"; 20 static NSString *firstCellID = @"first"; 21 22 //註冊可重用cell 23 if (!_isRegistNib) { 24 NSLog(@"registernib"); 25 UINib *nib = [UINib nibWithNibName:@"CustomTableCellDefaultView" bundle:nil]; 26 [tableView registerNib:nib forCellReuseIdentifier:defaultCellID]; 27 28 UINib *nibFirst = [UINib nibWithNibName:@"HomeTableCellFirstView" bundle:nil]; 29 [tableView registerNib:nibFirst forCellReuseIdentifier:firstCellID]; 30 _isRegistNib = !_isRegistNib; 31 } 32 NSInteger sectionIndex = indexPath.section; 33 34 35 if (sectionIndex % 2 == 0) { 36 //如果是第一個區域,顯示歡迎的cell 37 HomeTableCellFirst *cell = (HomeTableCellFirst *)[tableView dequeueReusableCellWithIdentifier:firstCellID forIndexPath:indexPath]; 38 NSLog(@"建立第一個cell"); 39 NSLog(@"cell的地址是:%@",cell); 40 NSLog(@"--------------------------------------"); 41 return cell; 42 } 43 else if(sectionIndex % 2 == 1){ 44 //如果是第二個區域,顯示普通的cell 45 CustomTableCellDefault *cell = (CustomTableCellDefault *)[tableView dequeueReusableCellWithIdentifier:defaultCellID forIndexPath:indexPath]; 46 NSLog(@"建立普通的cell"); 47 NSLog(@"cell的地址是:%@",cell); 48 NSLog(@"--------------------------------------"); 49 cell.lblStoreName.text = [NSString stringWithFormat:@"店家%ld",(indexPath.row+1)]; 50 return cell; 51 } 52 else{return nil;} 53 54 55 56 } 57 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 58 { 59 if (indexPath.section % 2 == 0) { 60 return 150; 61 } 62 return 80; 63 } 64 65 -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 66 { 67 return 10; 68 }
上面的tableview實在ib中拖進去的,style時group
在程式碼中設定了section的數量是4,在- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
方法中,使用registerNib:forCellReuseIdentifier方法註冊了兩種自定義的cell,並且會列印cell的地址及其他資訊,先來看下執行效果
剛開啟view的時候,tableview一共載入了[cell1]一個,[cell2] 7個。
當我們繼續向上滾動的時候,因為當前[cell2]的數量並不能充滿整個window,所以還會繼續建立cell2的物件,與此同時,唯一的一個cell1消失在window的時候,被tableview扔進了重用列表。
通過上面的圖,看到nslog列印出來的cell指向的地址,很清楚,我們建立了7個不同的[cell2]物件
當繼續滾動的時候,會繼續列印出和上面地址不重複的[cell2],大家可以自己試試,我就不上圖了
因為我讓第三個section又顯示了[cell1],所以繼續向下滾動,當它出現時,控制檯列印的cell的地址是0x7fbc2176c620,和第一個cell是同一個物件,因為使用了重用列表中的那個[cell1]的物件。
通過上面的實驗,可以總結下面的結論:
使用重用機制後:
1、tableview至少會建立可視區域高度 / cell高度 個 cell物件,因為當第一個cell隱藏了一半時,意味著要還要建立一個新的cell
2、建立了足夠多的cell後,再顯示cell就會使用可重用佇列中的cell
以上是本人自己的理解,如果不對的地方,還希望和大家多多交流。