iOS性能優化過程淺析

一:性能優化策略

性能問題的處理流程

1 發現/重現問題
2 利用工具剖析
3 形成假設
4 改進代碼和設計

在以上的四個步驟中循環反覆,直到問題解決。

Profile!不要猜!

性能優化的主要策略:

1、不要做無用功:不要在啓動時花幾百ms來做logging,不要爲同樣的數據做多次查詢
試圖重用:對於創建過程昂貴的對象,要重用而不是重新創建
1 Table View的cell
2 Date/Number的formatter
3 正則表達式
4 SQLite語句

2、使用更快的方式設計、編程:選擇正確的集合對象和算法來進行編程、選擇適合的數據存儲格式(plist、SQLite)、優化SQLite查詢語句
3、事先做優化
1 對於昂貴的計算,要進行事先計算。iCal中的重複事件,是預先計算出來的,並保存到數據庫中。
2 事先計算並緩存一些對象,可能會佔用大量的內存。注意不要將這些對象聲明爲static並常駐內存。

4、事後做優化:異步加載、懶加載
5、爲伸縮性而做優化:當數據有10條、100條、1000條甚至更多的時候,應用程序的性能不應該對應的呈數量級式的增長,否則無法使用。
6、給iOS app做加密保護優化:本地數據加密、URL編碼加密、網絡傳輸數據加密、方法體,方法名高級混淆、程序結構混排加密

二:iOS應用啓動速度優化

很多app的開發者都不重視app的啓動速度,這對於碎片化使用情景的用戶來說,簡直是災難。

iOS應用的啓動速度

應用啓動時,會播放一個放大的動畫。iPhone上是400ms,iPad上是500ms。最理想的啓動速度是,在播放完動畫後,用戶就可以使用。

如果應用啓動過慢,用戶就會放棄使用,甚至永遠都不再回來。拋開代碼不談,如果抱着PC端遊和單機遊戲的思維,在遊戲啓動時強加公司Logo,啓動動畫,並且用戶不可跳過,也會使用戶的成功使用率大大降低。

iOS系統的“看門狗”

爲了防止一個應用佔用過多的系統資源,開發iOS的蘋果工程師門設計了一個“看門狗”的機制。在不同的場景下,“看門狗”會監測應用的性能。如果超出了該場景所規定的運行時間,“看門狗”就會強制終結這個應用的進程。開發者們在crashlog裏面,會看到諸如0x8badf00d這樣的錯誤代碼(“看門狗”吃了壞的食物,它很不高興)。

場景  “看門狗”超時時間
啓動  20秒
恢復運行  10秒
懸掛進程  10秒
退出應用  6秒
後臺運行  10分鐘
值得注意的是,Xcode在Debug的時候,會禁止“看門狗”。

如何測試啓動時間

兩種方法:一種使用NSLog,另外一種使用Time Profiler。

1 使用NSLog
[pre]
複製代碼
  1. CFAbsoluteTime StartTime; int main(int argc, char **argv) { StartTime = CFAbsoluteTimeGetCurrent(); // ...  5 }  6    - (void)applicationDidFinishLaunching:(UIApplication *)app {        dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime); }); // ...  }
[/pre]
2 使用Time Profiler
1)Instruments->Time Profiler
2)Profile你的app
3)切換到CPU strategy view,找到你的app啓動的第一幀
4)搜索-[UIApplication _reportAppLaunchFinished]
5)找到包含-[UIApplication _reportAppLaunchFinished]的最後一幀,即可計算出啓動時間

iOS App啓動過程

1 鏈接並加載Framework和static lib
2 UIKit初始化
3 應用程序callback
4 第一個Core Animation transaction

鏈接並加載Framework及static lib時需要注意:

1 每個Framework都會增加啓動時間和佔用的內存
2 不必要的Framework,不要鏈接
3 必要的Framework,不要票房爲Optional
4 只在使用在Deployment Target之後發佈的Framework時,才使用Optional(比如你的Deployment Target是iOS 3.0,需要鏈接StoreKit的時候)
5 避免創建全局的C++對象

 初始化UIKit時需要注意:

1 字體、狀態欄、user defaults、main nib會被初始化
2 保持main nib儘可能的小
3 User defaults本質上是一個plist文件,保存的數據是同時被反序列化的,不要在user defaults裏面保存圖片等大數據

應用程序的回調:

1 application:willFinishLaunchingWithOptions:
2 恢復應用程序的狀態
3 application:didFinishLaunchingWithOptions:

我一直認爲設計的本質是折衷。當你爲了100ms的啓動速度優化歡欣不已,而無視那長達10秒的啓動動畫時,應該想想究竟什麼是應該做的。做正確的事情比把事情做好更重要。

三:事件處理-拯救主線程

用戶經常評論app的一個用詞是“卡頓”,很大的因素是因爲主線程被佔用了。用戶的事件是在主線程被處理的,包括點擊、滾動、加速計、Proximity Sensor。

爲了保證事件的平滑處理,需要進行如下優化:

1 最小化主線程的CPU佔用
2 將工作“搬離”主線程
3 不要阻塞主線程

最小化主線程的CPU佔用

使用Time Profiler可以剖析不同線程的CPU使用情況,並給出調用堆棧的CPU時間佔用百分比。如果app“卡頓”,並且在Time Profiler的結果可以找到明確的高佔用堆棧,你需要把它優化掉。

將工作“搬離”主線程 – 隱式併發

爲了得到更流暢的交互體驗,iOS已經幫我們做了很多事情,Android就沒有這麼好運了。iOS將以下這些事情搬離了主線程:

1 View和layer的動畫(動畫繪製前的計算,而不是drawing過程)
2 Layer的組合計算(drawing後的疊加)
3 PNG的解碼(是的,你沒看錯;而且利用了CPU的多核心)

注意滾動(Scrolling)不是一個動畫,而是在Main Run Loop中不斷接收事件並且處理。

將工作“搬離”主線程 – 顯式併發

這裏是需要開發者們搞定的部分。磁盤、網絡等I/O會阻塞線程,不要把它們放到主線程裏。常用的技術有:

1 Grand Central Dispatch(GCD)
2 NSOperationQueue
3 NSThread

iOS 4.0後,易用的GCD技術被廣泛使用。例如:

[pre]
複製代碼
  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // do something in background     dispatch_async(dispatch_get_main_queue(), ^{         // do something on main thread     }); });
[/pre]

GCD的陷阱

GCD其實就是線程,只不過提供了一個更高層次的抽象。過多的線程一定會帶來性能損失,因此GCD設計了一個最高允許的線程值(對開發者透明,不用管到底有多少)。那麼如何解決這個問題呢?

1 將隊列串行化
2 使用Dispatch sources
3 使用帶有限制的NSOperationQueue
4 使用Cocoa Touch提供的異步方法

另外一個陷阱是線程安全:

1 UIKit必須要在主線程使用,除了UIGraphics,UIBezierPath,UIImage
2 大多數CG、CA、Foundation的類,不是線程安全的
3 如果你使用了ojbc runtime來進行introspection,由於它是thread safe的,可能會導致競爭

此外,iOS 4.3添加了DISPATCH_QUEUE_PRIORITY_BACKGROUND,它擁有非常低的優先級。這個優先級只用於不太關心完成時間的真正的後臺任務,如果要表示較低的優先級,你通常需要的是DISPATCH_QUEUE_PRIORITY_LOW。

不要阻塞主線程

即使佔用了很少的CPU時間(如果你在Time Profiler中看到這些的數據),也可能會阻塞主線程。磁盤、網絡、Lock、dispatch_sync以及向其它進程/線程發送消息都會阻塞主線 程。Time Profiler只能檢測出佔用CPU過多的堆棧,但檢測不了這些IO的問題。

大多數的阻塞事件,都會伴隨着一個系統調用,如:

1 read/write - 讀寫文件
2 send/recv - 收發網絡數據
3 psynch_mutex_wait - 獲得鎖
4 mach_msg - IPC

System Trace這個Instrumentor,記錄了所有的系統調用,以及每次調用的等待時間。如果你在System Trace裏面發現了CPU Time很低,但Wait Time很高的調用,說明在主線程處理I/O已經嚴重損害了app的性能。

保證主線程的低CPU佔用,將I/O移至其它線程,可以大大地提高主線程對交互事件的處理能力。我建議開發者朋友們寫完iOS app代碼,可以去試下iOS app加密,這對iOS性能優化是有一定好處的!

發佈了11 篇原創文章 · 獲贊 5 · 訪問量 144萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章