iOS專項總結git
一個應用通過屢次迭代後告一段落,接下去咱們在技術上還能夠作些什麼呢?答案是提升代碼的總體質量。關於這方面,除了咱們常喊的 重構,測試也很是重要。程序員
博主近期給咱們的 iOS客戶端代碼來了一次專項測試。主要從常規的 輔助測試 入手,來了次代碼大清理,找到代碼中的問題,並一一改掉它們。驚喜的是,這對於提升本人的代碼水平有很大的幫助。其實,這套代碼的質量自己已經很高了,也很是整潔。而這主要得益於嚴格的代碼規範和pull request機制。github
關於測試,App常關注的每每是一些功能性的,包括單元測試,用monkey在界面上點擊看頁面表現是否正確等等。 我之前還搭過一個 aspectJ + robotium。(這是Java上的) 然而,測試更應該覆蓋代碼質量,性能檢測等等。算法
下面給出一副我理解測試的結構圖:服務器
順着這幅圖,咱們能夠從靜態測試入手。網絡
關於 analyzeapp
在Xcode 中執行product -> analyze工具,出現了粗粗細細的箭頭標識表示調用邏輯。async
這個工具所找出的主要是代碼中的邏輯問題,自己能發現的問題不少。實際運用到工程代碼上,發現的問題主要分爲如下四點:工具
好比初始化賦值未被使用過。oop
好比未調用super語句。
好比從未使用過的變量。
好比邏輯上錯誤的代碼。(這點極少)
可是關於資源文件,該工具是掃描不出來的。另外,咱們能夠在Xcode中配置該工具。
Clang 靜態分析器
analyze 的底層其實就是 Clang 靜態分析器,包含 ’shallow’ 和 ‘deep’ 深淺兩種模式。
深模式比淺模式慢不少,由於多出了跨方法的 控制流分析 以及 數據流分析。(如前文那副圖)
建議:
開啓分析器的 所有檢查(方法是在 build setting 的 「Static Analyzer」 部分開啓全部選項)
在 build setting 裏,對 release 的 build 配置開啓 「Analyzer during ‘Build’「。(真的,必定要這樣作 — 你不會記得手動跑分析器的。)
把 build setting 裏的 「Model of Analysis for ‘Analyze’「設爲 Shallow(faster)
把 build setting 裏的 「Model of Analysis for ‘Build’「設爲 Deep
但這兩種對於公司這麼一個大項目來講都不快,假如在 build setting裏 開啓 Model of Analysis for ‘Build’ 並設爲淺模式,那麼每次運行就會總體跑一遍靜態分析並不合理。
還有一種設想是在打 ad hoc 包前自動跑一遍 靜態分析,是比較合理的也能夠在XCode中開啓。可是咱們編譯打包用的是自動化工具,服務器直接從github讀取而不是經過XCode,會跳過這個分析。
比較合理的作法是按期 跑一遍 analyzer 工具。(好比每週五)而不是 build , release 之前作這事。(問了下其餘同事的意見)
目前靜態分析在工程中有 這麼幾塊不改的問題: 首先是網上拷過來的算法。包括加密算法,模糊算法,64位編譯算法等等。裏面有一些遠大於,遠小於,與運算等。看不懂的地方不改。
爲何不改呢?由於沒看出邏輯問題。(或者看懂了也不敢改,怕算法錯誤。)
Slender
slender 是一款針對 iOS 圖片分析的 Mac 軟件。導入工程,能夠找到全部未使用到的 resource。(unused 這軟件也能夠找到多餘的資源)
除此之外,它還給出了全部資源文件的建議。包括你缺省的 x3, x2圖片版本等。
Faux Pas
意外發現的驚喜。Faux Pas 是一個出色的靜態 Error 檢測工具。
slender 找到的是未使用的資源,faux 找到的是代碼中使用到,但不存在的image。(實測下來每每有不少)
甚至由此 發現了一些棄用的文件夾。
不只如此,它有一百多個error 和 warning 規則。包括 未定義的 user define runtime attributes(跑在runtime上)。這一點平時靠程序員的肉眼狠難見。曾經你爲了圖省事直接在 xib 中炫技,加上了一些 key。然而當代碼變遷,刪掉一個類的時候,根本想不到 到 xib中去刪除 對應的部分。
還有一些代碼規範上的問題。
好比界面上的文字未本地化;在release 版本依舊有NSLog 輸出;NSPhotoLibraryUsageDescription在info.plist中未定義;被定義成strong類型的delegate等等。
和一些配置上的建議。
Warning
你的pods 中還在報 warning 嗎?你可使用下面這種方法屏蔽它們。
在podfile 的開頭加上這樣一句話:
inhibit_all_warnings!
就能屏蔽 pods 中全部代碼上的 warning了。
而後惋惜的是,對於編譯上的 warning卻一籌莫展。注:iOS中不能阻止 Xcode 報warning 的選項。只能屏蔽某一文件,或某工程的warning。好比咱們已屏蔽的pods中 代碼的錯誤。可是屏蔽不了系統在編譯過程當中 非代碼級別的 警告。
而且也不建議屏蔽這種警告,好比因爲友盟未支持 7.0 版本所致使的 警告。如今因爲 友盟自己未支持,7.0 用戶還有許多等等緣由,但將來或許能解決。不該忘記這點。
Leaks
蘋果集成了一個性能檢測工具,叫instruments。使用instrument中的Leaks工具:
iOS內存泄露點比較少,大部分都是系統內部方法,或者一些庫裏的。咱們對此別無辦法。實測下來,主要的泄露場景集中在 UIKeyboard 上。在我嘗試了各類點擊場景後,系統彈框時候最容易泄露。
假如你的代碼中真有泄露,那它們可能集中在快速滑動 Listing的時候,加載大批量圖片的時候。在咱們的應用中,這些大都使用了網上開源的成熟庫。
對於 iOS可能泄露的地方,個人建議仍是經過已知的代碼規範 逆向去尋找。好比 誤用 strong類型的delegate; block 中沒有使用 weak self等等。
Time Profiler
時間都去哪兒了。這個工具能夠找出咱們最耗時的操做並定位到代碼中。
實際使用過程當中,確實發現了一處不合理的耗時循環。
(右側黑色部分是耗時操做,但不必定是錯誤的代碼)
工具只是統計時間的消耗,更重要的是經過對代碼的敏感,分析定位出可優化的代碼。舉個例子,有這麼一段代碼。
有個循環在判斷時候使用了枚舉類型,但因爲枚舉類型獲取不到count,因此直接寫死了循環10次。
但其實咱們能夠在枚舉類型的末尾加上TYPE_MAX。這樣既擁有了枚舉在switch時候的優點,又獲得了NSArray獲取count的辦法。
加載時間
加載時間上,主要看看不一樣網絡下出首頁有沒有卡死等。公司wifi條件真機上測試大約 1.2s 內加載完畢,而當模擬邊緣網絡時主線程加載依然迅速,沒發現什麼問題。
關於啓動時間,有兩種查看方法。一種是經過上文的時間檢測工具,另外一種是直接在代碼中打log。其中log又有兩種打法。
分別說下這兩種打法:
CFAbsoluteTime StartTime;
int main(int argc, char **argv) {
StartTime = CFAbsoluteTimeGetCurrent();
// ...
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime);
});
// ...
}
這是一般的打印方法。然而,從main 開始啓動有太多的不可控因素。
iOS App啓動過程
連接並加載Framework和static lib
UIKit初始化
應用程序callback
第一個Core Animation transaction
其中 framework的加載過程咱們難以控制,而初始化字體、狀態欄、user defaults、main nib等的過程,咱們也不須要關心。固然,你能夠作的是保持它們儘量小,沒有冗餘。
因此,還有一種作法是記錄你在 didFinishLaunchingWithOptions 中所耗費的時間。在這裏,你可能初始化了許多沒必要須的第三方庫,作了幾回網絡請求。而這些,咱們均可以 lazyLoad,讓程序儘快地冷啓動。
幀率等
如何優雅地顯示幀率標籤?
在 QuartzCore.framework裏,有一個 CADisplayLink 類。系統在渲染每一幀的時候,都會調用一次CADisplayLink中的 selector, 它有點像一個定時器類,默認爲一秒調用60次。
幀率大部分時間都穩定在 60fps,然而有兩種狀況下會致使它不能以每秒60次的頻率調用回調方法。
CPU忙於其餘計算,無暇執行屏幕繪製動做。
執行回調方法所用時間大於重繪每幀的間隔時間。
咱們能夠利用這個類的特性,寫出以下代碼來實時顯示一個 屏幕幀率 的標籤。
CADisplayLink displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- (void)tick:(CADisplayLink *)displayLink {
//...
self.numberOfFrames++;
NSTimeInterval delta = displayLink.timestamp - self.lastTimestamp;
// Less than one second
if (delta < 1) {
return;
}
CGFloat fps = self.numberOfFrames / delta;
//Update label
....
}
這個fps 的數值,即是每秒幀數。實測當下拉刷新,或加載List過快時,都會往下掉得很厲害。而這即是性能瓶頸所在的關鍵點。