- tableview 性能優化方法總覽
- tableViewCell 複用
- 緩存 cell 高度
- 圓角優化
- 異步繪製
- 其餘優化
- 一些優化方案的對比
heightForRowAtIndexPath:
是調用最頻繁的方法)
tableViewCell複用介紹 tableView 內部有一個 cell池,裏面放的就是你以前建立過的 cell 。內存豐富時會保存一些 UITableViewCell 對象放入到 cell 池,在須要調用的時候迅速的返回,而不用建立。內存吃緊時 cell 池會自動清理一些多餘的 UITableViewCell 對象。至於有多少 cell ,這個內部會自動控制。
注意:重取出來的 cell 是有可能捆綁過數據或者加過子視圖的,因此,若是有必要,要清除數據(如 label 的邊框),從而使其顯示正確的內容。html
tableviewCell 複用的方法
dequeueReusableCellWithIdentifier:forIndexPath:
(iOS6引入)ios
// 必須與register方法配套使用,不然返回的cell可能爲nil,會crash
[slef.myTableView registerClass:[MyCell class] forCellReuseIdentifier:NSStringFromClass([MyCell class])];
MyCell* cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([MyCell class]) forIndexPath:indexPath];
複製代碼
註冊不一樣類型的 cell 或者不復用,此處以不復用爲例git
@property (nonatomic, strong) NSMutableDictionary *cellDic;//放cell的標識符
// 每次先從字典中根據IndexPath取出惟一標識符
NSString *identifier = [_cellDic objectForKey:[NSString stringWithFormat:@"%@", indexPath]];
// 若是取出的惟一標示符不存在,則初始化惟一標示符,並將其存入字典中,對應惟一標示符註冊Cell
if (identifier == nil) {
identifier = [NSString stringWithFormat:@"%@%@", @"cell", [NSString stringWithFormat:@"%@", indexPath]];
[_cellDic setValue:identifier forKey:[NSString stringWithFormat:@"%@", indexPath]];
// 註冊Cell
[self.tableview registerClass:[MyCell class] forCellWithReuseIdentifier:identifier];
}
MyCell *cell = [tableView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
複製代碼
UITableView 複用機制原理:
查看UITableView頭文件,會找到NSMutableArray *visiableCells,和NSMutableDictionary *reusableTableCells兩個結構。其中visiableCells用來存儲當前UITableView顯示的cell,reusableTableCells用來存儲已經用'identify'緩存的cell。當UITableView滾動的時候,會先在reusableTableCells中根據identify找是否有有已經緩存的cell,若是有直接用,沒有再去初始化。(TableView顯示之初,reusableTableCells爲空,那麼tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。開始的cell都是經過[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]來建立,並且cellForRowAtIndexPath只是調用最大顯示cell數的次數)github
@property (nonatomic, strong) NSMutableDictionary *heightAtIndexPath;//緩存高度所用字典
#pragma mark - UITableViewDelegate-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath]; if(height){
return height.floatValue;
}else {
return 100;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{ NSNumber *height = @(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}
複製代碼
fd_heightForCellWithIdentifier: configuration:
方法會根據 identifier 以及 configuration block 提供一個和 cell 佈局相同的 template layout cell,並將其傳入 fd_systemFittingHeightForConfiguratedCell:
這個私有方法返回計算出的高度。主要使用技術爲 runtime 。OpenGL中,GPU屏幕渲染有如下兩種方式: On-Screen Rendering:意思是當前屏幕渲染,指的是GPU的渲染操做是在當前用於顯示的屏幕緩衝區進行。 Off-Screen Rendering:意思就是咱們說的離屏渲染了,指的是GPU在當前屏幕緩衝區之外新開闢一個緩衝區進行渲染操做。緩存
相比於當前屏幕渲染,離屏渲染的代價是很高的,主要體如今兩個方面:性能優化
離屏渲染觸發條件bash
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];
複製代碼
UIImageView *imageView = [[UIImageViewalloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image = [UIImageimageNamed:@"myImg"];
UIBezierPath *maskPath = [UIBezierPathbezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
//設置大小
maskLayer.frame=imageView.bounds;
//設置圖形樣子
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.viewaddSubview:imageView];
複製代碼
對於方案2須要解釋的是: CAShapeLayer繼承於CALayer,可使用CALayer的全部屬性值;CAShapeLayer須要貝塞爾曲線配合使用纔有意義(也就是說纔有效果)使用CAShapeLayer(屬於CoreAnimation)與貝塞爾曲線能夠實現不在view的drawRect(繼承於CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形CAShapeLayer動畫渲染直接提交到手機的GPU當中,相較於view的drawRect方法使用CPU渲染而言,其效率極高,能大大優化內存使用狀況。 總的來講就是用CAShapeLayer的內存消耗少,渲染速度快,建議使用優化方案2。數據結構
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AsyncLabel : UIView
@property (nonatomic, copy) NSString *asynText;
@property (nonatomic, strong) UIFont *asynFont;
@property (nonatomic, strong) UIColor *asynBGColor;
@end
NS_ASSUME_NONNULL_END
#import "AsyncLabel.h"
#import <CoreText/CoreText.h>
@implementation AsyncLabel
- (void)displayLayer:(CALayer *)layer {
/**
除了在drawRect方法中, 其餘地方獲取context須要本身建立[https://www.jianshu.com/p/86f025f06d62]
coreText用法簡介:[https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html]
*/
CGSize size = self.bounds.size;;
CGFloat scale = [UIScreen mainScreen].scale;
///異步繪製:切換至子線程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
///獲取當前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
///將座標系反轉
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
///文本沿着Y軸移動
CGContextTranslateCTM(context, 0, size.height);
///文本反轉成context座標系
CGContextScaleCTM(context, 1.0, -1.0);
///建立繪製區域
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
///建立須要繪製的文字
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:self.asynText];
[attStr addAttribute:NSFontAttributeName value:self.asynFont range:NSMakeRange(0, self.asynText.length)];
[attStr addAttribute:NSBackgroundColorAttributeName value:self.asynBGColor range:NSMakeRange(0, self.asynText.length)];
///根據attStr生成CTFramesetterRef
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attStr);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path, NULL);
///將frame的內容繪製到content中
CTFrameDraw(frame, context);
UIImage *getImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
///子線程完成工做, 切換到主線程展現
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = (__bridge id)getImg.CGImage;
});
});
}
@end
#import "ViewController.h"
#import "AsyncLabel.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
AsyncLabel *asLabel = [[AsyncLabel alloc] initWithFrame:CGRectMake(50, 100, 200, 200)];
asLabel.backgroundColor = [UIColor cyanColor];
asLabel.asynBGColor = [UIColor greenColor];
asLabel.asynFont = [UIFont systemFontOfSize:16 weight:20];
asLabel.asynText = @"學習異步繪製相關知識點, 學習異步繪製相關知識點";
[self.view addSubview:asLabel];
///不調用的話不會觸發 displayLayer方法
[asLabel.layer setNeedsDisplay];
}
@end
複製代碼
- (void)loadData{
// 開闢子線程處理數據
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 處理數據 coding...
// 返回主線程處理
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainTableView reloadData];
});
});
複製代碼
不要作多餘的繪製工做
在實現drawRect:的時候,它的rect參數就是須要繪製的區域,這個區域以外的不須要進行繪製。 例如上例中,就能夠用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判斷是否須要繪製image和text,而後再調用繪製方法。框架
如圖,這個label顯示的內容由model的兩個參數(時間、千米數)拼接而成,咱們習慣在cell裏model的set方法中這樣賦值異步
//時間
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterMediumStyle;
formatter.timeStyle = NSDateFormatterShortStyle;
[formatter setDateFormat:@"yyyy年MM月"];
NSDate* date = [NSDate dateWithTimeIntervalSince1970:[model.licenseTime intValue]];
NSString* licenseTimeString = [formatter stringFromDate:date];
//千米數
NSString *travelMileageString = (model.travelMileage != nil && ![model.travelMileage isEqualToString:@""]) ? [NSString stringWithFormat:@"%@萬千米",model.travelMileage] : @"里程暫無";
//賦值給label.text
self.carDescribeLabel.text = [NSString stringWithFormat:@"%@ / %@",licenseTimeString,travelMileageString];
複製代碼
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 15.0 + 80.0 + 15.0;
}
複製代碼
修改成static float ROW_HEIGHT = 15.0 + 80.0 + 15.0;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return ROW_HEIGHT;
}
複製代碼
固然這不是減小對象的建立,而是減小了計算的次數,減小了頻繁調用方法裏的邏輯,從而達到更快的速度。文章推薦:
VVeboTableViewDemo
性能優化-UITableView的優化使用
iOS 保持界面流暢的技巧