開發完 iOS 應用,接下去你該做的事

發表於2016-10-14

iOS專項總結

一個應用經過多次迭代後告一段落,接下去我們在技術上還可以做些什麼呢?答案是提高程式碼的整體質量。關於這方面,除了我們常喊的 重構,測試也非常重要。

博主近期給我們的 iOS客戶端程式碼來了一次專項測試。主要從常規的 輔助測試 入手,來了次程式碼大清理,找到程式碼中的問題,並一一改掉它們。驚喜的是,這對於提高本人的程式碼水平有很大的幫助。其實,這套程式碼的質量本身已經很高了,也非常整潔。而這主要得益於嚴格的程式碼規範和pull request機制。

關於測試,App常關注的往往是一些功能性的,包括單元測試,用monkey在介面上點選看頁面表現是否正確等等。 我以前還搭過一個 aspectJ + robotium。(這是Java上的) 然而,測試更應該覆蓋程式碼質量,效能檢測等等。
下面給出一副我理解測試的結構圖:

afterios_testblue

順著這幅圖,我們可以從靜態測試入手。

關於 analyze

在Xcode 中執行product -> analyze工具,出現了粗粗細細的箭頭標識表示呼叫邏輯。

12afterios_analyze

這個工具所找出的主要是程式碼中的邏輯問題,本身能發現的問題很多。實際運用到工程程式碼上,發現的問題主要分為以下四點:

  • 比如初始化賦值未被使用過。
  • 比如未呼叫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圖片版本等。

13afterios_slender

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工具:

15afterios_leaks

iOS記憶體洩露點比較少,大部分都是系統內部方法,或者一些庫裡的。我們對此別無辦法。實測下來,主要的洩露場景集中在 UIKeyboard 上。在我嘗試了各種點選場景後,系統彈框時候最容易洩露。

假如你的程式碼中真有洩露,那它們可能集中在快速滑動 Listing的時候,載入大批量圖片的時候。在我們的應用中,這些大都使用了網上開源的成熟庫。

對於 iOS可能洩露的地方,我的建議還是通過已知的程式碼規範 逆向去尋找。比如 誤用 strong型別的delegate; block 中沒有使用 weak self等等。

Time Profiler

時間都去哪兒了。這個工具可以找出我們最耗時的操作並定位到程式碼中。

實際使用過程中,確實發現了一處不合理的耗時迴圈。

16afterios_timeprofiler

(右側黑色部分是耗時操作,但不一定是錯誤的程式碼)

工具只是統計時間的消耗,更重要的是通過對程式碼的敏感,分析定位出可優化的程式碼。舉個例子,有這麼一段程式碼。

有個迴圈在判斷時候使用了列舉型別,但由於列舉型別獲取不到count,所以直接寫死了迴圈10次。

但其實我們可以在列舉型別的末尾加上TYPE_MAX。這樣既擁有了列舉在switch時候的優勢,又得到了NSArray獲取count的辦法。

載入時間

載入時間上,主要看看不同網路下出首頁有沒有卡死等。公司wifi條件真機上測試大約 1.2s 內載入完畢,而當模擬邊緣網路時主執行緒載入依然迅速,沒發現什麼問題。

關於啟動時間,有兩種檢視方法。一種是通過上文的時間檢測工具,另一種是直接在程式碼中打log。其中log又有兩種打法。

分別說下這兩種打法:


這是通常的列印方法。然而,從main 開始啟動有太多的不可控因素。

iOS App啟動過程

  • 連結並載入Framework和static lib
  • UIKit初始化
  • 應用程式callback
  • 第一個Core Animation transaction

其中 framework的載入過程我們難以控制,而初始化字型、狀態列、user defaults、main nib等的過程,我們也不需要關心。當然,你可以做的是保持它們儘可能小,沒有冗餘。

所以,還有一種做法是記錄你在 didFinishLaunchingWithOptions 中所耗費的時間。在這裡,你可能初始化了許多不必須的第三方庫,做了幾次網路請求。而這些,我們都可以 lazyLoad,讓程式儘快地冷啟動。

幀率等

如何優雅地顯示幀率標籤?

afterios_fps

QuartzCore.framework裡,有一個 CADisplayLink 類。系統在渲染每一幀的時候,都會呼叫一次CADisplayLink中的 selector, 它有點像一個定時器類,預設為一秒呼叫60次。

幀率大部分時間都穩定在 60fps,然而有兩種情況下會導致它不能以每秒60次的頻率呼叫回撥方法。

  1. CPU忙於其他計算,無暇執行螢幕繪製動作。
  2. 執行回撥方法所用時間大於重繪每幀的間隔時間。

我們可以利用這個類的特性,寫出如下程式碼來實時顯示一個 螢幕幀率 的標籤。


這個fps 的數值,便是每秒幀數。實測當下拉重新整理,或載入List過快時,都會往下掉得很厲害。而這便是效能瓶頸所在的關鍵點。

相關文章