關(guān)于性能優(yōu)化的一些經(jīng)驗(yàn)

1.視圖view優(yōu)化 預(yù)處理和延時(shí)加載
2.UITableView優(yōu)化
3.緩存優(yōu)化 重用大開銷對(duì)象。對(duì)于初始化很慢的對(duì)象通過添加屬性的方式保持該對(duì)象,保證只被初始化一次,多次復(fù)用。如NSDataFormatter。
4.線程優(yōu)化 合理的線程分配
5.內(nèi)存優(yōu)化 使用autorelease pool 降低內(nèi)存峰值
6.代碼細(xì)節(jié)優(yōu)化 使用正確API 如果關(guān)鍵代碼用C/C++效率更高就使用C/C++
7.圖片優(yōu)化

卡頓產(chǎn)生的原因和解決方案
在 VSync 信號(hào)到來后,系統(tǒng)圖形服務(wù)會(huì)通過 CADisplayLink 等機(jī)制通知 App,App 主線程開始在 CPU 中計(jì)算顯示內(nèi)容,比如視圖的創(chuàng)建、布局計(jì)算、圖片解碼、文本繪制等。隨后 CPU 會(huì)將計(jì)算好的內(nèi)容提交到 GPU 去,由 GPU 進(jìn)行變換、合成、渲染。隨后 GPU 會(huì)把渲染結(jié)果提交到幀緩沖區(qū)去,等待下一次 VSync 信號(hào)到來時(shí)顯示到屏幕上。由于垂直同步的機(jī)制,如果在一個(gè) VSync 時(shí)間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會(huì)被丟棄,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。

用 Instuments 的 GPU Driver 預(yù)設(shè),能夠?qū)崟r(shí)查看到 CPU 和 GPU 的資源消耗。在這個(gè)預(yù)設(shè)內(nèi),你能查看到幾乎所有與顯示有關(guān)的數(shù)據(jù),比如 Texture 數(shù)量、CA 提交的頻率、GPU 消耗等,在定位界面卡頓的問題時(shí),這是最好的工具。

CPU 資源消耗原因和解決方案
對(duì)象創(chuàng)建 延遲創(chuàng)建的時(shí)間 盡量放到緩存池里復(fù)用。
對(duì)象調(diào)整 盡量避免調(diào)整視圖層次、添加和移除視圖
對(duì)象銷毀 盡量放到后臺(tái)線程銷毀
布局計(jì)算 不要頻繁的計(jì)算和調(diào)整這些屬性
Autolayout 用一些工具方法替代(比如常見的left/right/top/bottom/width/height 屬性),或者使用 ComponentKit、AsyncDisplayKit 等框架
文本計(jì)算 NSAttributedString放到后臺(tái)線程進(jìn)行以避免阻塞主線程
文本渲染 CoreText排版和繪制
圖片解碼
圖像繪制 后臺(tái)異步繪制 CoreGraphic

GPU 資源消耗原因和解決方案
GPU 能干的事情比較單一:接收提交的紋理(Texture)和頂點(diǎn)描述(三角形),應(yīng)用變換(transform)、混合并渲染,然后輸出到屏幕上。
紋理渲染
視圖混合
圖形生成
CALayer 的 border、圓角、陰影、遮罩(mask),CASharpLayer 的矢量圖形顯示,通常會(huì)觸發(fā)離屏渲染(offscreen rendering),而離屏渲染通常發(fā)生在 GPU 中。最徹底的解決辦法,就是把需要顯示的圖形在后臺(tái)線程繪制為圖片,避免使用圓角、陰影、遮罩等屬性。

UIView 和 CALayer 的關(guān)系:View 持有 Layer 用于顯示,View 中大部分顯示屬性實(shí)際是從 Layer 映射而來;Layer 的 delegate 在這里是 View,當(dāng)其屬性改變、動(dòng)畫產(chǎn)生時(shí),View 能夠得到通知。UIView 和 CALayer 不是線程安全的,并且只能在主線程創(chuàng)建、訪問和銷毀。

圓角的問題(Offscreen rendering的問題)

離屏渲染是什么?

OpenGL中,GPU屏幕渲染有以下兩種方式: On-Screen Rendering 意為當(dāng)前屏幕渲染,指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)中進(jìn)行。 Off-Screen Rendering 意為離屏渲染,指的是GPU在當(dāng)前屏幕緩沖區(qū)以外新開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作。

相比于當(dāng)前屏幕渲染,離屏渲染的代價(jià)是很高的,主要體現(xiàn)在兩個(gè)方面: 創(chuàng)建新緩沖區(qū) 要想進(jìn)行離屏渲染,首先要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū)。 上下文切換 離屏渲染的整個(gè)過程,需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上有需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕。而上下文環(huán)境的切換是要付出很大代價(jià)的。哪些行為會(huì)導(dǎo)致Offscreen rendering?

custom drawRect: (any, even if you simply fill the background with color)
CALayer corner radius
CALayer shadow
CALayer mask
any custom drawing using CGContext

例如你的工程里的XXCell.m

- (void)setupViews {
   _textLabel = [[UILabel alloc]init];
   [_textLabel setTextAlignment:NSTextAlignmentCenter];
   _textLabel.font = [UIFont systemFontOfSize:14];
   [self.contentView addSubview:_textLabel];

   self.contentView.layer.masksToBounds = YES;
   self.contentView.layer.cornerRadius = 4;
}

這種寫法導(dǎo)致了離屏渲染。

如何解決?

圓角使用UIImageView來處理。
簡單來說,底層鋪一個(gè)UIImageView,然后用GraphicsContext生成一張帶圓角的圖。

@implementation UIImage (RoundedCorner)

- (UIImage *)wt_imageWithRoundedCornersAndSize:(CGSize)sizeToFit andCornerRadius:(CGFloat)radius
{
     CGRect rect = (CGRect){0.f, 0.f, sizeToFit};

     UIGraphicsBeginImageContextWithOptions(sizeToFit, NO, UIScreen.mainScreen.scale);
     CGContextAddPath(UIGraphicsGetCurrentContext(),
                 [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);
     CGContextClip(UIGraphicsGetCurrentContext());

     [self drawInRect:rect];
     UIImage *output = UIGraphicsGetImageFromCurrentImageContext();

     UIGraphicsEndImageContext();

     return output;
}

@end

這樣,就可以避免在大量cell離屏渲染的時(shí)候拖慢fps。
當(dāng)然,如果只是一兩個(gè)view有圓角的需求,你大可不必這么折騰。直接設(shè)置layer.cornerRadius就可以了。

設(shè)置圓角的方法也有很多種。

1.cornerRadius
2.UIBezierPath

- (void)drawRect:(CGRect)rect {
    CGRect bounds = self.bounds;
    [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.0] addClip];

    [self.image drawInRect:bounds];
}
3.maskLayer(CAShapeLayer) 相關(guān)鏈接:Applying Rounded Corners  http://articles.cocoahope.com/blog/2013/03/06/applying-rounded-corners

  Mastering UIKit performance  https://yalantis.com/blog/mastering-uikit-performance/

重載Autolayout的問題

- (void)refreshConstraints {
    [self.view setNeedsUpdateConstraints];
    [self.view updateConstraintsIfNeeded];
    [self.view layoutIfNeeded];
    [self.view setNeedsDisplay];
}

setNeedsUpdateConstraints 確保在未來的某個(gè)階段updateConstraintsIfNeeded這個(gè)方法會(huì)調(diào)用 updateConstraints.
setNeedsLayout 確保在未來的某個(gè)階段 layoutIfNeeded 這個(gè)方法會(huì)調(diào)用 layoutSubviews.
直接調(diào)用setNeedsUpdateConstraints去更新約束是很奇怪的,因?yàn)橹挥性谀承┣闆r下你的某個(gè)約束失效了,你需要移除它,這時(shí)候才需要直接調(diào)用這個(gè)方法。 stackoverflow鏈接
上面這個(gè)答案說的已經(jīng)很好了,我只用翻譯一邊就好了。

主題色的問題

一個(gè)app的各種控件的顏色有很多種處理方法。
一般常見的有。

寫成JSON文件,然后設(shè)置一個(gè)Category讀取。
直接在UIColor的Category里設(shè)置各種方法獲取相對(duì)應(yīng)的顏色。
Raywenderlich的一篇相關(guān)文章
相對(duì)比使用最多的方法,比如這種。

[self.view setBackgroundColor:[UIColor colorWithHexString:@"F3F3F3"]];  
[_textLabel setTextColor:[UIColor colorWithHexString:@"969696"]];
self.contentView.backgroundColor = [UIColor clearColor];
self.contentView.layer.borderColor = [UIColor colorWithHexString:@"969696"].CGColor;            [_textLabel setTextColor:[UIColor colorWithHexString:@"009788"]];     self.contentView.backgroundColor = [UIColor clearColor];
self.contentView.layer.borderColor = [UIColor colorWithHexString:@"009788"].CGColor;

無法閱讀,也不好改。
或者,用這個(gè)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容