前言
- 不要提前過度優化
- 要找到性能瓶頸
- 要在不同性能指標間權衡
- 要理解優化任務的底層運行機制
- 要有技術保障體系
一、啟動速度優化
1.1 學習文章
- WWDC 啟動速度優化視頻 Session 406 Optimizing App Startup Time
- iOS性能(二) 啟動時間優化
1.2 操作步驟
查看啟動時間
配置 Xcode 環境變量在日志中打印啟動時間:
- 打開工程 -> Edit Scheme -> Run -> Environment Variables
根據需要添加
DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS環境變量。value 設置為 1(表示 YES),開啟這個功能。
Total pre-main time: 617.58 milliseconds (100.0%) dylib loading time: 472.75 milliseconds (76.5%)
rebase/binding time: 27.01 milliseconds (4.3%)
ObjC setup time: 28.90 milliseconds (4.6%)
initializer time: 88.76 milliseconds (14.3%)
slowest intializers :
libSystem.B.dylib : 8.81 milliseconds (1.4%)
libMainThreadChecker.dylib : 14.42 milliseconds (2.3%)
AFNetworking : 18.43 milliseconds (2.9%)
Realm : 20.98 milliseconds (3.3%)
CYKJBasic : 12.96 milliseconds (2.0%)包括執行以下步驟的所有時間:
- 解析鏡像
- 映射鏡像
- Rebase 鏡像
- Bind 鏡像
- 鏡像初始化
- 調用 main()方法
- 調用 UIApplicationMain() 方法
- 調用 applicationWillFinishLaunching 回調
優化
main()函數之后的執行時間- 使用代碼繪制 UI,減少或者不用 xib 和 storyboard
- 延遲初始化和加載不必要的 UIViewController 和 View。
- 使用后台線程處理耗時的任務
- 能延遲初始化的盡量延遲初始化
優化
main()函數之前的執行時間Session 所要傳達的內容:
- 使用 DYLD_PRINT_STATISTICS 測試啟動加載時間
- 減少自定義的動態庫集成
- 精簡原有的 Objective-C 類和代碼
- 移除靜態的初始化操作
- 使用更多的 Swift 代碼
優化:
loading dylib
減少動態庫的數量。盡量保證將 App 現有的非系統級的動態庫個數保證在 6 個以內。采取的方式:1、合並動態庫;2、使用靜態庫。
rebase/binding
Rebase 和 Binding 都是為了解決指針引用的問題。對於 Objective-C 開發來說,主要的時間消耗在 Class/Method 的符號加載上,所以常見的優化方案是:
- 減少 __DATA 段中的指針數量。
- 合並 Category 和功能類似的類,減少唯一 Selector 的個數。
- 刪除無用的方法和類。
- 多用 Swift Structs,因為 Swfit Structs 是靜態分發的。
- 減少 c++ 虛函數
ObjC setup time
盡量減少類的數量,可以達到減少這一部分的時間。
Initializers
- 用 initialize 替代 load。(注意:要根據實際情況替換)
- 減少使用 c/c++ 的 __atribute__((constructor))。推薦使用 dispatch_once()、pthread_once()、std:once()等方法;
- 推薦使用 swift
- 不要在初始化中調用 dlopen() 方法,因為加載過程是單線程,無鎖,如果調用 dlopen 則會變成多線程,會開啟鎖的消耗,同時有可能死鎖
- 不要在初始化中創建線程
1.3 實際處理
實際處理時因為要考慮團隊的原因,所以只采用了一下三個步驟:
- 刪除不需要的三方庫,因為工程中用 use_frameworks! pod 進來的庫是動態庫。
- 將大部分 +load 方法改為 +initialize 方法,保留部分必要的 +load 方法;
- 動態庫 -> 靜態庫,技術上實現了,討論后最終棄用。
- 刪除無用的方法和類,減少 rebase/binding 時間。。
1.4 處理效果
慈雲 app 啟動時間有 700 ~ 850ms,降為 600 ~ 700ms。
如果采用靜態方式 pod,可以降為 450 ~ 550ms。
二、接口請求優化
主要集中處理一級 tab 的切換體驗,頻繁的調用接口,頻繁的刷新界面顯然是影響用戶體驗的。優化的思路有以下幾點:
使用 loading 框 + 默認灰色矩形視圖;

使用本地緩存
每隔 15s(或者 10s) 以上才請求一次,防止頻繁觸發請求
@property (nonatomic, assign) CFTimeInterval lastTi; - (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CFTimeInterval nowTi = CACurrentMediaTime();
// 10 秒內不請求
if (nowTi - self.lastTi > 10) {
self.reqData.pageNo = 1;
[self.service requestPatientConsultList:self.reqData];
self.lastTi = nowTi;
}
}
- (void)makeRequestAction
{
// 接口請求
}CACurrentMediaTime() 在退到后台、手動修改設備時間后沒有影響。
對數據進行判斷,數據沒有更新不需要刷新界面。
@property (nonatomic, copy) NSString * primaryCompareMD5; // 初始值 @""
self.primaryCompareMD5 = @"";
SELF_WEEK;
// 異步執行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
// 這里的 data 是要判斷的接口數據
NSString * string = [data componentsJoinedByString:@""];
if (!string) {
string = @"";
}
NSString * md5 = [CYDXUtil doMd5:string];
SELF_STRONG;
// 數據未發生改變,直接返回
if ([strongSelf.primaryCompareMD5 isEqualToString:md5]) {
return;
}
strongSelf.primaryCompareMD5 = md5;
// 主線程刷新界面
dispatch_sync(dispatch_get_main_queue(), ^{
[strongSelf.tableView reloadData];
});
}
});
這里的策略還是存在點問題:
- 不能處理好未讀標識。看實際情況,處理從子界面返回時的邏輯。
三、界面滑動優化
3.1 監測界面卡頓工具
- 使用系統的 Instrument - CoreAnimation
- 使用系統的 Instrument - Time Profiler
- YYFPSLabel。
在未滑動時,Instrument - CoreAnimation 顯示的是 0 FPS。

YYFPSLabel 顯示的是 60FPS。

卡頓問題修改后,Instrument - CoreAnimation 需要在設備上運行一遍,然后重新用工具檢測;YYFPSLabel 可以直接加入工程,可視化,更加的友好,方便。
Time Profiler 可以查看哪些方法占用時間多,優化那些可以優化的。

3.2 優化處理

圖層混合
Xcode 頂部菜單欄 -> Debug -> View Debugging -> Rendering -> Color Blended Layers
正常的為綠色;出現圖層混合時為紅色。
- 確保控件的 opaque 屬性設置為 true,確保 backgroundColor 和父視圖顏色一致且不透明;
- 如無特殊需要,不要設置低於 1 的 alpha 值;
- 確保 UIImage 沒有 alpha 通道;
圖片縮放
Xcode 頂部菜單欄 -> Debug -> View Debugging -> Rendering -> Color Misaligned Images
如果圖片需要縮放則標記為黃色,如果沒有像素對齊則標記為紫色。
這個看情況是否優化,是否需要 UI 配合。
- 確保圖片大小和frame一致,不要在滑動時縮放圖片;
- 確保圖片顏色格式被 GPU 支持,避免勞煩 CPU 轉換;
離屏渲染
Xcode 頂部菜單欄 -> Debug -> View Debugging -> Rendering -> Color Offscreen-Rendered Yellow
觸發離屏渲染的地方標記為黃色。
下面的情況或操作會引發離屏渲染:
- 為圖層設置遮罩(layer.mask)
- 同時設置 layer.masksToBounds 和 corneRadius 屬性設置為 true
- 將圖層的 layer.allowsGroupOpacity 屬性設置為 YES 和 layer.opacity < 1.0
- 為圖層設置陰影(layer.shadow*)
- 為圖層設置 layer.shouldRasterize = true
- 文本(任何種類,包括 UILabel、CATextLayer、CoreText 等)
- 使用 CGContext 在 drawRect: 方法中繪制大部分情況下會導致離屏渲染,甚至僅僅是一個空的實現
解決:
- 使用 CoreGraphics 繪制圓角,不使用 mask 或 masksToBounds + corneRadius;
- 設置陰影是使用 shadowPath;
- UILabel 如果不是透明的,設置 opaque = YES,Clip To bounds = YES,backgroundColor;
- 設置圖片圓角可以讓 UI 切圖,也可以使用 CoreGraphics 繪制圖片的方式。在 iOS 9.0 以上,圖片設置圓角不會觸發離屏渲染。
UITableViewCell 優化
- Cell 復用;
- 提前計算並緩存 Cell 的高度;
- 減少 subviews 的個數和層級;
- 少用 subviews 的透明圖層;
- 如果不是透明視圖,背景色不要使用 clearColor;
- 注意離屏渲染問題;
- 圖片提前在子線程異步解碼,SDWebImage 已經實現;
- 異步繪制(自定義 Cell 繪制)VVeboTableViewDemo、YYAsyncLayer、AsyncDisplayKit
- 滑動時,按需加載。注意:這個會導致滑動時出現大量空白,不友好。
- 盡量顯示“大小剛好合適的”圖片資源
- 避免同步的從網絡、文件獲取數據
四、內存占用
主要檢查工程中的內存泄露、循環引用的問題。
- instrument - Allocations 動態分析
- Analyze 靜態分析
- 騰訊 MLeaksFinder
- 臉書 FBMemoryProfiler
說明:
- MLeaksFinder 效果比較好,能查找工程中出現循環引用的的場景,在慈雲找到十幾處循環引用。
- 官方的 instrument 工具效果一般,在慈雲 app 只檢測出了 3 處循環引用,2 處內存泄露;
- Analyze 靜態分析可檢測出“創建了但未被使用的”變量,需要結合代碼邏輯,小心處理。
- 臉書的 FBMemoryProfiler 工具不大好用,主要是記錄內存的開辟與銷毀。
如果要更多的處理內存占用問題,需要分析工程中的代碼邏輯,通過使用不同的存儲方式、容器類、加載數據方式等,達到減少內存使用的目的。
五、縮小 ipa 包大小
安裝包大小的優化,主要包含兩大塊:資源大小的優化和二進制大小的優化。
5.1 資源大小的優化
資源大小的優化主要包括以下幾個方面:
- 資源壓縮
- 未使用、重復資源的刪除
- 資源上雲
具體步驟:
資源壓縮
Xcode 的編譯選項中,提供了
Compress PNG Files及Remove Text MetaData From PNG Files
但是由於 PNG 是無損壓縮,經過 Xcode 壓縮后的圖片資源,依然很大。
使用 pngquant 對大多數的 32 位圖進行了處理,將其轉為 8 位圖,並且使用 Zopfli 進行了壓縮,這樣整體的 PNG 圖片資源大概被壓縮了 70% 左右。這里要注意,由於一些漸變背景的顏色覆蓋范圍較大,轉為 8 位圖顏色丟失較大,表現效果會差很多,所以這些圖片要謹慎處理。
未使用、重復資源的刪除
資源上雲
資源上雲可以有效減少包內資源,唯一要注意的是這些資源由於是 lazy load,所以比較適合層級較深的頁面使用。
5.2 其他處理
配置編譯選項 Generate Debug Symbols 設置為 NO;

舍棄架構,如:armv7,根據實際選擇。

編譯的版本必須是 release 版本,
查找內部使用到的第三方庫,一方面可以進行刪減代碼,用不到的類,直接刪除,還有第三方庫中的圖片資源統統刪除掉,如果能夠自己手寫實現的,那費功夫自己寫吧
六、引用庫升級及替換
持續更新的第三方庫也會對性能做出優化,在替換前,需先確定不會給項目引入問題。
