UITableView的核心思想是:cell的重用機(jī)制。UITbleView只會(huì)創(chuàng)建一屏幕(或一屏幕多一點(diǎn))的cell, 每當(dāng)cell滑出屏幕時(shí),就會(huì)放倒一個(gè)集合(或數(shù)組)中(這里相當(dāng)于一個(gè)重用池),當(dāng)要顯示某一個(gè)位置的cell時(shí),會(huì)先根據(jù)ReuseIdentifier去集合和數(shù)組中去取,如果有直接拿來用,如果沒有的話,才會(huì)去創(chuàng)建,這樣極大地減少了內(nèi)存的開銷。
UITableView的兩個(gè)重要的回調(diào)方法是:tableView:cellForRowAtIndexPath:和tableView:heightForRowAtIndexPath:。由于UITableView是繼承自UIScrollView的,需要先確定它的contentSize及每個(gè)Cell的位置,然后才會(huì)把重用的cell放置到對應(yīng)的位置。UITableView的回調(diào)順序是先多次調(diào)用tableView:heightForRowAtIndexPath:來確定cell的位置,然后才會(huì)調(diào)用tableView:cellForRowAtIndexPath:從而來顯示在當(dāng)前的屏幕的cell。
舉個(gè)例子來說:如果現(xiàn)在要顯示100個(gè)Cell,當(dāng)前屏幕顯示5個(gè)。那么刷新(reload)UITableView時(shí),UITableView會(huì)先調(diào)用100次tableView:heightForRowAtIndexPath:方法,然后調(diào)用5次tableView:cellForRowAtIndexPath:方法;滾動(dòng)屏幕時(shí),每當(dāng)Cell滾入屏幕,都會(huì)調(diào)用一次tableView:heightForRowAtIndexPath:、tableView:cellForRowAtIndexPath:方法。
1:把賦值和計(jì)算布局分離。這樣讓tableView:cellForRowAtIndexPath:只負(fù)責(zé)賦值,tableView:heightForRowAtIndexPath:只負(fù)責(zé)計(jì)算高度。注意,這兩個(gè)方法各司其職。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary?*dict?=?self.dataList[indexPath.row];
return[ContacterTableCell?cellHeightOfInfo:dict];
}
基于上面的思路,我們可以在獲得數(shù)據(jù)后,直接根據(jù)數(shù)據(jù)源計(jì)算出對應(yīng)的布局,并緩存到數(shù)據(jù)源中,這樣在tableView:heightForRowAtIndexPath:方法中就直接返回高度,而不需要每次都計(jì)算了。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary?*dict?=?self.dataList[indexPath.row];
CGRect?rect?=?[dict[@"frame"]?CGRectValue];
returnrect.frame.height;
}
2:上面的方案并不是最佳的方案,可以滿足簡單的界面!如果像朋友圈那樣的圖文混排,這種方案其實(shí)扛不住的,自定義cell的繪制。在cell上添加系統(tǒng)的控件時(shí),實(shí)質(zhì)系統(tǒng)都需要調(diào)用底層的接口進(jìn)行繪制,如果大量的添加,對資源的開銷會(huì)很大,所以我們可以直接繪制,提高效率。
首先自定義cell的draw方法,(也可以重寫drawRect)然后在方法中實(shí)現(xiàn)
//異步繪制
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{
CGRect?rect?=?[_data[@"frame"]?CGRectValue];
UIGraphicsBeginImageContextWithOptions(rect.size,?YES,?0);//<CoreGraphics/CoreGraphics.h>
CGContextRef?context?=?UIGraphicsGetCurrentContext();
//整個(gè)內(nèi)容的背景
[[UIColor?colorWithRed:250/255.0?green:250/255.0?blue:250/255.0?alpha:1]?set];
CGContextFillRect(context,?rect);
//轉(zhuǎn)發(fā)內(nèi)容的背景
if([_data?valueForKey:@"subData"])?{
[[UIColor?colorWithRed:243/255.0?green:243/255.0?blue:243/255.0?alpha:1]?set];
CGRect?subFrame?=?[_data[@"subData"][@"frame"]?CGRectValue];
CGContextFillRect(context,?subFrame);
[[UIColor?colorWithRed:200/255.0?green:200/255.0?blue:200/255.0?alpha:1]?set];
CGContextFillRect(context,?CGRectMake(0,?subFrame.origin.y,?rect.size.width,?.5));
}
{
//名字
float?leftX?=?SIZE_GAP_LEFT+SIZE_AVATAR+SIZE_GAP_BIG;
float?x?=?leftX;
float?y?=?(SIZE_AVATAR-(SIZE_FONT_NAME+SIZE_FONT_SUBTITLE+6))/2-2+SIZE_GAP_TOP+SIZE_GAP_SMALL-5;
[_data[@"name"]?drawInContext:context?withPosition:CGPointMake(x,?y)?andFont:FontWithSize(SIZE_FONT_NAME)
andTextColor:[UIColor?colorWithRed:106/255.0?green:140/255.0?blue:181/255.0?alpha:1]
andHeight:rect.size.height];
//時(shí)間+設(shè)備
y?+=?SIZE_FONT_NAME+5;
float?fromX?=?leftX;
float?size?=?[UIScreen?screenWidth]-leftX;
NSString?*from?=?[NSString?stringWithFormat:@"%@??%@",?_data[@"time"],?_data[@"from"]];
[from?drawInContext:context?withPosition:CGPointMake(fromX,?y)?andFont:FontWithSize(SIZE_FONT_SUBTITLE)
andTextColor:[UIColor?colorWithRed:178/255.0?green:178/255.0?blue:178/255.0?alpha:1]
andHeight:rect.size.height?andWidth:size];
}
//將繪制的內(nèi)容以圖片的形式返回,并調(diào)主線程顯示
UIImage?*temp?=?UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(),?^{
if(flag==drawColorFlag)?{
postBGView.frame?=?rect;
postBGView.image?=?nil;
postBGView.image?=?temp;
}
}
//內(nèi)容如果是圖文混排,就添加View,用CoreText繪制
[self?drawText];
}}
大體的思路是這個(gè)情況,各個(gè)信息都是根據(jù)之前算好的布局進(jìn)行繪制的。這里是需要異步繪制,但如果在重寫 drawRect方法就不需要用GCD異步線程了,因?yàn)閐rawRext本來就是異步繪制的。
3:進(jìn)行異步繪制這樣的話,UITableView的效率提高了一個(gè)等級(jí)!不過我們還可以從UIScrollerView的角度出發(fā),再次找到突破口
滑動(dòng)UITableView時(shí)按需加載對應(yīng)的內(nèi)容:
//按需加載 - 如果目標(biāo)行與當(dāng)前行相差超過指定行數(shù),只在目標(biāo)滾動(dòng)范圍的前后指定3行加載。
-?(void)scrollViewWillEndDragging:(UIScrollView?*)scrollView?withVelocity:(CGPoint)velocity?targetContentOffset:(inout?CGPoint?*)targetContentOffset{
NSIndexPath?*ip?=?[self?indexPathForRowAtPoint:CGPointMake(0,?targetContentOffset->y)];
NSIndexPath?*cip?=?[[self?indexPathsForVisibleRows]?firstObject];
NSInteger?skipCount?=?8;
if(labs(cip.row-ip.row)>skipCount)?{
NSArray?*temp?=?[self?indexPathsForRowsInRect:CGRectMake(0,?targetContentOffset->y,?self.width,?self.height)];
NSMutableArray?*arr?=?[NSMutableArray?arrayWithArray:temp];
if(velocity.y<0)?{
NSIndexPath?*indexPath?=?[temp?lastObject];
if(indexPath.row+33)?{
[arr?addObject:[NSIndexPath?indexPathForRow:indexPath.row-3?inSection:0]];
[arr?addObject:[NSIndexPath?indexPathForRow:indexPath.row-2?inSection:0]];
[arr?addObject:[NSIndexPath?indexPathForRow:indexPath.row-1?inSection:0]];
}
}
[needLoadArr?addObjectsFromArray:arr];
}
}
記得在tableView:cellForRowAtIndexPath:方法中加入判斷:
if(needLoadArr.count>0&&[needLoadArr indexOfObject:indexPath]==NSNotFound) {
[cell?clear];
return;
}
滾動(dòng)很快時(shí),只加載目標(biāo)范圍內(nèi)的Cell,這樣按需加載,極大的提高流暢度。
總結(jié):UITableView的優(yōu)化主要從三個(gè)方面入手
(1)提前計(jì)算并緩存好高度,因?yàn)閔eightForRowAtIndexPath:是調(diào)用最頻繁的方法
(2)異步繪制,遇到復(fù)雜的界面,遇到性能瓶頸時(shí),可能就是突破點(diǎn)
(3)滑動(dòng)時(shí)按需加載,這個(gè)在大量的圖片展示,網(wǎng)絡(luò)加載的時(shí)候很管用!
除了以上最主要的三個(gè)方面,還有其他的優(yōu)化點(diǎn):
?正確使用reuseIdentifier來重用cells
?盡量使所有的view opaque,包括cell自身。
opaque屬性提示繪制系統(tǒng)如何處理view。如果opaque設(shè)置為YES,繪圖系統(tǒng)會(huì)將view看為完全不透明,這樣會(huì)繪圖系統(tǒng)就可以優(yōu)化一些繪制操作以提升性能。如果設(shè)置為NO,那么繪圖系統(tǒng)結(jié)合其它的內(nèi)容來處理view。默認(rèn)情況下,這個(gè)屬性是YES.
?盡量少用或不用透明圖層
?如果cell內(nèi)的顯示的內(nèi)容來自web,使用異步加載,緩存請求結(jié)果
?減少subviews的數(shù)量
?在heightForRowAtIndexPath:中盡量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后緩存結(jié)果
?盡量少用addView給Cell動(dòng)態(tài)添加View,可以初始化時(shí)就添加,然后通過hide來控制是否顯示