iOS效能優化
tableView
CPU
和GPU
在螢幕成像的過程中,CPU
和GPU
起著至關重要的作用。
-
CPU
(Central Processing Unit
,中央處理器)
負責:物件的建立和銷燬、物件屬性的調整、佈局計算、文字的計算和排版、圖片的格式轉換和解碼、影象的繪製(Core Graphics
)。
-
GPU
(Graphics Processing Unit
,圖形處理器)
負責:紋理的渲染。
graph LR; CPU-->|計算|GPU; GPU-->|渲染|幀快取; 幀快取-->|讀取|視訊控制器; 視訊控制器-->|顯示|螢幕;
CPU
計算好的資料給GPU
,GPU
來渲染,渲染後的資料放在幀快取(緩衝區,有兩塊緩衝區,前幀快取和後幀快取,協調使用,效率高)中。然後,視訊控制器
從緩衝區獲取渲染後的資料顯示在螢幕
上。
-
在
iOS
中是雙緩衝機制, 有前幀快取、後幀快取。
螢幕成像原理
一幀(或者一頁)資料就是:一個垂直同步訊號(VSync
)和一個水平同步訊號(HSync
)的組合。
先發送一個垂直同步訊號(VSync
),代表即將顯示一頁,再發送一個水平同步訊號(HSync
)就顯示一幀。
卡頓產生的原因
graph LR; CPU計算-->GPU渲染計算; GPU渲染計算-->VSync訊號 VSync訊號-.-....;
當下次VSync
訊號到來之前,CPU
和GPU
還沒有計算完成,就會產生卡頓效果。
- 卡頓解決的主要思路
儘可能減少CPU
、GPU
資源消耗。
-
按照
60FPS
的刷幀率,每隔16ms
就會有一次VSync
訊號。
卡頓優化 - CPU
-
儘量用輕量級的物件,比如用不到事件處理的地方,可以考慮使用
CALayer
取代UIView
。 -
不要頻繁地呼叫
UIView
的相關屬性, 比如frame
、bounds
、transform
等屬性,儘量減少不必要的修改。 -
儘量提前計算好佈局,在有需要時一次性調整對應的屬性,不要多次修改屬性。
-
Autolayout
會比直接設定frame
消耗更多的CPU
資源。 -
圖片的
size
最好剛好跟UIImageView
的size
保持一致。 -
控制一下執行緒的最大併發數量。
-
儘量把耗時的操作放到子執行緒。
文字處理(尺寸計算、繪製)
圖片處理(解碼、繪製)
// 文字計算 [@"text" boundingRectWithSize:CGSizeMake(100, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:nil context:nil]; // 文字繪製 [@"text" drawWithRect:CGRectMake(0, 0, 100, 100) options:NSStringDrawingUsesLineFragmentOrigin attributes:nil context:nil];
graph LR; UIImage+imageNamed:-->|壓縮|二進位制檔案; 二進位制檔案-->|解碼|螢幕所需要的格式; 螢幕所需要的格式-->渲染到螢幕;
圖片解碼
的過程預設是在主執行緒,如果圖片比較多資料比較大,就會產生卡頓。提前在子執行緒解碼
。
// 圖片的處理,提前解碼 - (void)image { UIImageView *imageView = [[UIImageView alloc] init]; imageView.frame = CGRectMake(100, 100, 100, 56); [self.view addSubview:imageView]; self.imageView = imageView; dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 獲取CGImage CGImageRef cgImage = [UIImage imageNamed:@"timg"].CGImage; // alphaInfo CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage) & kCGBitmapAlphaInfoMask; BOOL hasAlpha = NO; if (alphaInfo == kCGImageAlphaPremultipliedLast || alphaInfo == kCGImageAlphaPremultipliedFirst || alphaInfo == kCGImageAlphaLast || alphaInfo == kCGImageAlphaFirst) { hasAlpha = YES; } // bitmapInfo CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; // size size_t width = CGImageGetWidth(cgImage); size_t height = CGImageGetHeight(cgImage); // 解碼:把點陣圖提前畫到圖形上下文,生成 cgImage,就完成了解碼。 // context CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGColorSpaceCreateDeviceRGB(), bitmapInfo); // draw CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); // get CGImage cgImage = CGBitmapContextCreateImage(context); // 解碼後的圖片,包裝成 UIImage 。 // into UIImage UIImage *newImage = [UIImage imageWithCGImage:cgImage]; // release CGContextRelease(context); CGImageRelease(cgImage); // back to the main thread dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = newImage; }); }); }
卡頓優化 - GPU
-
儘量避免短時間內大量圖片的顯示,儘可能將多張圖片合成一張進行顯示。
-
GPU
能處理的最大紋理尺寸是4096x4096
,一旦超過這個尺寸,就會佔用CPU
資源進行處理,所以紋理儘量不要超過這個尺寸。 -
儘量減少檢視數量和層次。
-
減少透明的檢視(
alpha<1
),不透明的就設定opaque
為YES
。(透明檢視疊加,需要計算疊加部分顏色。) -
儘量避免出現離屏渲染。
離屏渲染
-
在
OpenGL
中,GPU
有2
中渲染方式:On-Screen Rendering
:當前螢幕渲染,在當前用於顯示的螢幕緩衝區進行渲染操作。Off-Screen Rendering
:離屏渲染,在當前螢幕緩衝區以外新開闢一個緩衝區進行渲染操作。
- 離屏渲染消耗效能的原因:
需要建立新的緩衝區。
離屏渲染的整個過程,需要多次切換上下文環境。先是從當前螢幕(On-Screen
)切換到離屏(Off-Screen
);等到離屏渲染結束以後,將離屏緩衝區的渲染結果顯示到螢幕上,又需要將上下文環境從離屏切換到當前螢幕。
-
哪些操作會觸發離屏渲染?
光柵化,
layer.shouldRasterize = YES
遮罩,
layer.mask
圓角,同時設定
layer.masksToBounds = YES
、layer.cornerRadius
大於0。可以考慮通過
CoreGraphics
繪製裁剪圓角,或者讓美工提供圓角圖片。
-
陰影,
layer.shadowXXX
如果設定了layer.shadowPath
就不會產生離屏渲染。
卡頓檢測
- 平時所說的“卡頓”主要是因為線上程執行了比較耗時的操作。
-
可以新增
Observer
到主執行緒RunLoop
中,通過監聽RunLoop
狀態切換的耗時,以達到監控卡頓的目的。
耗電
耗電的主要來源
-
CPU
處理,Processing
-
網路,
Networking
-
定位,
Location
-
影象,
Graphics
耗電優化
-
儘可能降低
CPU
、GPU
功耗 -
少用定時器
-
優化
I/O
操作儘量不要頻繁寫入小資料,最好批量一次性寫入。
讀寫大量重要資料時,考慮用
dispatch_io
,其提供了基於GCD
的非同步操作檔案I/O
的API
。用dispatch_io
系統會優化磁碟訪問。資料量比較大的,建議使用資料庫(比如
SQLite
、CoreData
) -
網路優化
減少、壓縮網路資料。XML
:體積比較大。JSON
:體積比較小。protobuf
。
graph LR; XML-->JSON; JSON-->protobuf(protocol buffer);
如果多次請求的結果是相同的,儘量使用快取。(一個請求內容不變的情況,NSMutableURLRequest
中可以使用NSCache
快取。)
使用斷點續傳,否則網路不穩定時可能多次傳輸相同的內容
網路不可用時,不要嘗試執行網路請求
讓使用者可以取消長時間執行或者速度很慢的網路操作,設定合適的超時時間
批量傳輸,比如,下載視訊流時,不要傳輸很小的資料包,直接下載整個檔案或者一大塊一大塊地下載。如果下載廣告,一次性多下載一些,然後再慢慢展示。如果下載電子郵件,一次下載多封,不要一封一封地下載。
- 定位優化
如果只是需要快速確定使用者位置,最好用CLLocationManager
的requestLocation
方法。定位完成後,會自動讓定位硬體斷電。
如果不是導航應用,儘量不要實時更新位置,定位完畢就關掉定位服務。
儘量降低定位精度,比如儘量不要使用精度最高的KCLLocationAccuracyBest
。
需要後臺定位時,儘量設定pausesLocationUpdatesAutomatically
為YES
, 如果使用者不太可能移動的時候系統會自動暫停位置更新。
儘量不要使用startMonitoringSignificantLocationChanges
優先考慮startMonitoringForRegion:
。
- 硬體檢測優化
使用者移動、搖晃、傾斜裝置時,會產生動作(motion
)事件,這些事件由加速計、陀螺儀、磁力計等硬體檢測。在不需要檢測的場合,應該及時關閉這些硬體。
App 的啟動
-
App 的啟動可以分為
2
種:
冷啟動(Cold Launch
):從零開始啟動App
熱啟動(Warm Launch
):App
已經在記憶體中,在後臺存活著,再次點選圖示啟動App
。
-
App
啟動時間的優化,主要是針對冷啟動進行優化。 -
通過新增環境變數可以打印出
App
的啟動時間分析(Edit scheme -> Run -> Arguments
)DYLD_PRINT_STATISTICS
設定為1
。如果需要更詳細的資訊,那就將DYLD_PRINT_STATISTICS_DETAILS
設定為1
。(總啟動時間差不多400ms內就是正常)
-
App
的冷啟動可以概括為3大階段:
dyld
runtime
main
App 的啟動dyld
-
dyld
(dynamic link editor
),Apple
的動態聯結器,可以用來裝載Mach-O
檔案(可執行檔案、動態庫等) -
啟動
App
時,dyld
所做的事情有
裝載App
的可執行檔案,同時會遞迴載入所有依賴的動態庫。
當dyld
把可執行檔案、動態庫都裝載完畢後,會通知Runtime
進行下一步的處理。
App 的啟動 - runtime
-
啟動
App
時,runtime
所做的事情有:-
呼叫
map_images
進行可執行檔案內容的解析和處理
-
在
load_images
中呼叫call_load_methods
,呼叫所有Class
和Category
的+load
方法
-
進行各種
objc
結構的初始化(註冊Objc
類、初始化類物件等等)
-
呼叫
C++
靜態初始化器和__attribute__((constructor))
修飾的函式
-
呼叫
-
到此為止,可執行檔案和動態庫中所有的符號(
Class
,Protocol
,Selector
,IMP
,…)都已經按格式成功載入到記憶體中,被runtime
所管理。
App 的啟動 - main
- 總結一下
App
的啟動由dyld
主導,將可執行檔案載入到記憶體,順便載入所有依賴的動態庫。
並由runtime
負責載入成objc
定義的結構。
所有初始化工作結束後,dyld
就會呼叫main
函式。
接下來就是UIApplicationMain
函式,AppDelegate
的application:didFinishLaunchingWithOptions:
方法
App 的啟動優化
-
按照不同的階段
-
dyld
減少動態庫、合併一些動態庫(定期清理不必要的動態庫)
減少Objc
類、分類的數量、減少Selector
數量(定期清理不必要的類、分類)
減少C++
虛擬函式數量
Swift
儘量使用struct
- runtime
用+initialize
方法和dispatch_once
取代所有的__attribute__((constructor))
、C++
靜態構造器、Objective-C
的+load
。
// 替代 +load + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ }); }
- main
在不影響使用者體驗的前提下,儘可能將一些操作延遲,不要全部都放在finishLaunching
方法中。
按需載入。
安裝包瘦身
-
安裝包(
ipa
)主要由可執行檔案、資源組成。 -
資源(圖片、音訊、視訊等)
採取無失真壓縮
去除沒有用到的資源:https://github.com/tinymind/LSUnusedResources
- 可執行檔案瘦身
編譯器優化
Strip Linked Product
、Make Strings Read-Only
、Symbols Hidden by Default
設定為YES
。
去掉異常支援,Enable C++ Exceptions
、Enable Objective-C Exceptions
設定為NO
,Other C Flags
新增-fno-exceptions
利用AppCode
(ofollow,noindex">https://www.jetbrains.com/objc/)檢測未使用的程式碼:`選單->Code->Inspect
Code`
編寫LLVM
外掛檢測出重複程式碼、未被呼叫的程式碼。
- LinkMap
生成LinkMap
檔案, 可以檢視可執行檔案的具體組成。
可藉助第三方工具解析LinkMap
檔案:https://github.com/huanxsd/LinkMap