iOS包羅萬象之底層

目錄

1、連接器

2、極速編譯調(diào)試

3、靜態(tài)分析 工具。

4、如何利用 Clang 為 App 提質(zhì)?

5、無侵入的埋點方案如何實現(xiàn)?

6、多線程的那些坑

7、耗電優(yōu)化

8、RunLoop精講

9、PromiseKit 異步事件鏈編程?PromiseKit

10、simdjson高性能JSON解析框架?https://time.geekbang.org/column/article/93819?(simdjson 是一款他們研究了很久的快速 JSON 解析器, 號稱每秒可解析千兆字節(jié) JSON 文件。)

11、富文本渲染:(WebView 、 YYText)網(wǎng)頁預加載框架?STMURLCache

12、測試框架:BDD 的 Objective-C 框架有?kiwi、Specta、Expecta等,Swift 框架有?Quick。

13、開發(fā)代碼規(guī)范

14、iOS學習資料(高級)

15、iOS內(nèi)存管理 (第三方內(nèi)存檢測工具有?MLeaksFinder、FBRetainCycleDetector、OOMDetector OOMDetector。)

0、進階開發(fā)

1、iOS開發(fā)提高(唐巧)https://blog.devtang.com/2014/07/27/ios-levelup-tips/

2、蘋果官方教程:https://developer.apple.com/tutorials/SwiftUI

3、APPdemo:https://www.appcoda.com

4、底層知識是最值得深挖的,不管哪個領域,殊途同歸,底層都是需要持續(xù)學習的。這里我推薦 Michael Ash 的:https://www.mikeash.com/book.html


1、鏈接器:符號是怎么綁定到地址上的?

1>iOS 編寫的代碼是先使用編譯器把代碼編譯成機器碼,然后直接在 CPU 上執(zhí)行機器碼的。之所以不使用解釋器來運行代碼,是因為蘋果公司希望 iPhone 的執(zhí)行效率更高、運行速度能達到最快。文件越多,鏈接器鏈接 Mach-O 文件所需綁定的遍歷操作就會越多,編譯速度也會越慢。

2>解釋器可以在運行時去執(zhí)行代碼,說明它具有動態(tài)性,程序運行后能夠隨時通過增加和更新代碼來改變程序的邏輯

2、App 如何通過注入動態(tài)庫的方式實現(xiàn) 極速編譯調(diào)試?動態(tài)庫鏈接器的實際應用,也就是編譯調(diào)試的提速問題。

1>iOS 原生代碼的 編譯調(diào)試,都是通過一遍又一遍地編譯重啟 App 來進行的。所以,項目代碼量越大,編譯時間就越長。雖然我們可以通過 「將部分代碼先編譯成二進制」集成到工程里,來避免每次都全量編譯來加快編譯速度

2>?原生代碼怎樣才能夠?qū)崿F(xiàn)動態(tài)極速調(diào)試

? ? ? ? ? 1》Swift Playground

? ? ? ? ? 2》Flutter Hot Reload

? ? ? ? ? 3》Injection for Xcode :John Holdsworth 開發(fā)了一個叫作 Injection 的工具可以動態(tài)地將 Swift 或 Objective-C 的代碼在已運行的程序中執(zhí)行,以加快調(diào)試速度,同時保證程序不用重啟。作者已經(jīng)開源了這個工具,地址是GitHub?。使用方式就是 clone 下代碼,構建 InjectionPluginLite/InjectionPlugin.xcodeproj ;刪除方式是,在終端里運行下面這行代碼:rm -rf ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins/InjectionPlugin.xcplugin

Injection 會監(jiān)聽源代碼文件的變化,如果文件被改動了,Injection Server 就會執(zhí)行 rebuildClass 重新進行編譯、打包成動態(tài)庫,也就是 .dylib 文件。編譯、打包成動態(tài)庫后使用 writeSting 方法通過 Socket 通知運行的 App。

Server 會在后臺發(fā)送和監(jiān)聽 Socket 消息,實現(xiàn)邏輯在 InjectionServer.mm 的 runInBackground 方法里。Client 也會開啟一個后臺去發(fā)送和監(jiān)聽 Socket 消息,實現(xiàn)邏輯在 InjectionClient.mm里的 runInBackground 方法里。Client 接收到消息后會調(diào)用 inject(tmpfile: String) 方法,運行時進行類的動態(tài)替換。inject(tmpfile: String) 方法的具體實現(xiàn)代碼,你可以點擊這個鏈接查看GitHub。

3、其他方法:可做參考?其他提高編譯速度方案

3、Clang、Infer 和 OCLint ,我們應該使用誰來做 ?靜態(tài)分析?隨著業(yè)務開發(fā)迭代速度越來越快,完全依賴人工保證工程質(zhì)量也變得越來越不牢靠。所以iOS 開發(fā)者需要一種代碼調(diào)試技術。

1>OCLint:(你可以在官方規(guī)則索引中,查看完整的規(guī)則說明。)OCLint 是基于 Clang Tooling 開發(fā)的靜態(tài)分析工具,主要用來發(fā)現(xiàn)編譯器檢查不到的那些潛在的關鍵技術問題。2017 年 9 月份新發(fā)布的 OCLint 0.13 版本中,包含了 71 條規(guī)則。這些規(guī)則已經(jīng)基本覆蓋了具有通用性的規(guī)則,主要包括語法上的基礎規(guī)則、Cocoa 庫相關規(guī)則、一些約定俗成的規(guī)則、各種空語句檢查、是否按新語法改寫的檢查、命名上長變量名短變量名檢查、無用的語句變量和參數(shù)的檢查。除此之外,還包括了和代碼量大小是否合理相關的一些規(guī)則,比如過大的類、類里方法是否太多、參數(shù)是否過多、Block 嵌套是否太深、方法里代碼是否過多、圈復雜度的檢查等。

OCLint 的安裝方式,我建議你使用 Homebrew 的方式。Homebrew 是 macOS 下專門用來進行軟件包管理的一個工具,使用起來很方便,讓你無需關心一些依賴和路徑配置。使用 Homebrew 的方式安裝時,我們需要首先設置 brew 的第三方倉庫,然后安裝 OCLint。安裝方法是在終端輸入:

?1 brew tap oclint/formulae ? 2 brew install oclint

安裝完成,先編寫一個 Hello world 代碼來測試下,創(chuàng)建一個 Hello.m 文件來編寫代碼,使用 OCLint 來檢查下前面編寫的 Hello.m ,在終端輸入如下命令:oclint Hello.m

然后,我們可以使用下面的命令,將檢查結(jié)果生成為一個 HTML 格式的報告:oclint -report-type html -o report.html Hello.m


2>Clang 靜態(tài)分析器:Clang 靜態(tài)分析器(Clang Static Analyzer)是一個用 C++ 開發(fā)的,用來分析 C、C++ 和 Objective-C 的開源工具,是 Clang 項目的一部分,構建在 Clang 和 LLVM 之上。Clang 靜態(tài)分析器的分析引擎用的就是 Clang 的庫。

你可以點擊這里這里下載?Clang 靜態(tài)分析器,然后 解壓就可以了,不需要放到特定目錄下。而卸載它的話,刪除這個解壓后的目錄即可。

Clang

scan-build 的原理是,將編譯器構建改成另一個“假的”編譯器來構建,這個“假的”編譯器會執(zhí)行 Clang 來編譯,然后執(zhí)行靜態(tài)分析器分析你的代碼。scan-build 的使用方法,也很簡單,你只需要到項目目錄下,使用如下命令即可:

\yourpath\scan-build -k -V make

關于 scan-build 的更多參數(shù)和使用說明,你可以點擊這個鏈接查看。

3>?Infer:Infer 是 Facebook 開源的、使用 OCaml 語言編寫的靜態(tài)分析工具,可以對 C、Java 和 Objective-C 代碼進行靜態(tài)分析,可以檢查出空指針訪問、資源泄露以及內(nèi)存泄露。

Infer 的安裝,有從源碼安裝和直接安裝 binary releases 兩種方式。

infer安裝

使用源碼安裝所需的時間會比較長,因為會編譯一個特定的 Clang 版本,而 Clang 是個龐大的工程,特別是第一次編譯的耗時會比較長。我在第一次編譯時,就大概花了一個多小時。所以,直接安裝 binary releases 會更快些,在終端輸入:

brew install infer

如何使用 Infer。我們可以先寫一段 Objective-C 代碼:

infer使用

Infer 的工作原理:

第一個階段是轉(zhuǎn)化階段,將源代碼轉(zhuǎn)成 Infer 內(nèi)部的中間語言。類 C 語言使用 Clang 進行編譯,Java 語言使用 javac 進行編譯,編譯的同時轉(zhuǎn)成中間語言,輸出到 infer-out 目錄。

第二個階段是分析階段,分析 infer-out 目錄下的文件。分析每個方法,如果出現(xiàn)錯誤的話會繼續(xù)分析下一個方法,不會被中斷,但是會記錄下出錯的位置,最后將所有出錯的地方進行匯總輸出。

默認情況下,每次運行 infer 命令都會刪除之前的 infer-out 文件夾。你可以通過 --incremental 參數(shù)使用增量模式。增量模式下,運行 infer 命令不會刪除 infer-out 文件夾,但是會利用這個文件夾進行 diff,減少分析量。一般進行全新一輪分析時直接使用默認的非增量模式,而對于只想分析修改部分情況時,就使用增量模式。

Infer 檢查的結(jié)果,在 infer-out 目錄下,是 JSON 格式的,名字叫做 report.json 。生成 JSON 格式的結(jié)果,通用性會更強,集成到其他系統(tǒng)時會更方便。

4> 總結(jié):其中 Clang 靜態(tài)分析器和 Xcode 的集成度高,也支持命令行。不過,它們檢查的規(guī)則少,基本都是只能檢查出較大的問題,比如類型轉(zhuǎn)換問題,而對內(nèi)存泄露問題檢查的側(cè)重點則在于可用性。

OCLint 檢查規(guī)則多、定制性強,能夠發(fā)現(xiàn)很多潛在問題。但缺點也是檢查規(guī)則太多,反而容易找不到重點;可定制度過高,導致易用性變差。

Infer 的效率高,支持增量分析,可小范圍分析??啥ㄖ菩圆凰阕顝?,屬于中等。綜合來看,Infer 在準確性、性能效率、規(guī)則、擴展性、易用性整體度上的把握是做得最好的,我認為這些是決定靜態(tài)分析器好不好最重要的幾點。所以,我比較推薦的是使用 Infer 來進行代碼靜態(tài)分析。


4、如何利用 Clang 為 App 提質(zhì)?

1>?基于 Clang 還可以開發(fā)出用于代碼增量分析、代碼可視化、代碼質(zhì)量報告來保障 App 質(zhì)量的系統(tǒng)平臺,比如 CodeChecker。

比如,當周末發(fā)現(xiàn)線上問題時,你會發(fā)現(xiàn)很多時候分析問題的人都不在電腦邊,無法及時處理問題。這時,我們就需要一款在線網(wǎng)頁代碼導航工具,比如 Mozilla 開發(fā)的 DXR,方便在便攜設備上去操作、分析問題,這樣的工具都是基于 Clang 開發(fā)的。

2>Clang有點:第一,對于使用者來說,Clang 編譯的速度非???,對內(nèi)存的使用率非常低,并且兼容 GCC ;第二,對于代碼診斷來說, Clang 也非常強大,Xcode 也是用的 Clang;第三,Clang 對 typedef 的保留和展開也處理得非常好;第四,F(xiàn)ix-it 提示也是 Clang 提供的一種快捷修復源碼問題的方式。在宏的處理上,很多宏都是深度嵌套的, Clang 會自動打印實例化信息和嵌套范圍信息來幫助你進行宏的診斷和分析;第五,Clang 的架構是模塊化的。除了代碼靜態(tài)分析外,利用其輸出的接口還可以開發(fā)用于代碼轉(zhuǎn)義、代碼生成、代碼重構的工具,方便與 IDE 進行集成。

缺點:GCC 對于 Objective-C 的支持比較差,效率和性能都沒有辦法達到蘋果公司的要求,而且它還難以推動 GCC 團隊。

于是,蘋果公司決定自己來掌握編譯相關的工具鏈,將天才克里斯·拉特納(Chris Lattner)招入麾下后開發(fā)了 LLVM 工具套件,將 GCC 全面替換成了 LLVM。這,也使得 Swift 這門集各種高級語言特性的語言,能夠在非常高的起點上,出現(xiàn)在開發(fā)者面前。(蘋果就是牛?。┠憧梢渣c擊這里的鏈接,在線查看 Clang 源碼。

3>Clang提供的能力:Clang 為一些需要分析代碼語法、語義信息的工具提供了基礎設施。這些基礎設施就是 LibClang、Clang Plugin 和 LibTooling。


5、無侵入的埋點方案如何實現(xiàn)?

1、解決問題:一是了解用戶使用 App 的行為,二是降低分析線上問題的難度。目前,iOS 開發(fā)中常見的埋點方式,主要包括 ?「代碼埋點」(手動寫代碼埋入)、「可視化埋點」(就是將埋點增加和修改的工作可視化了,提升了增加和維護埋點的體驗)和 「無埋點」(運行時方法替換方式進行埋點)這三種。

2、最常見的三種埋點,就是對 「頁面進入次數(shù)」、「頁面停留時間」、「點擊事件」的埋點。

3、這個方法利用運行時 method_exchangeImplementations 接口將方法的實現(xiàn)進行了交換,原方法調(diào)用時就會被 hook 住,從而去執(zhí)行指定的方法。

封裝hook

4、頁面進入次數(shù)、頁面停留時間都需要對 UIViewController 生命周期進行埋點。

區(qū)別不同的 UIViewController 呢?我一般采取的做法都是,使用 NSStringFromClass([self class]) 方法來取類名。這樣,我就能夠通過類名來區(qū)別不同的 UIViewController 了。

VC頁面統(tǒng)計埋點

5、事件統(tǒng)計:對于點擊事件來說,我們也可以通過運行時方法替換的方式進行無侵入埋點。這里最主要的工作是,找到這個點擊事件的方法 sendAction:to:forEvent:,然后在 +load() 方法使用 SMHook 替換成為你定義的方法。

UIButton 在一個視圖類中可能有多個不同的繼承類,相同 UIButton 的子類在不同視圖類的埋點也要區(qū)別開。所以,我們需要通過 “action 選擇器名 NSStringFromSelector(action)” +“視圖類名 NSStringFromClass([target class])”組合成一個唯一的標識,來進行埋點記錄。

事件

6、事件唯一標識:UITableViewCell 需要使用 indexPath,這個值里包含了 section 和 row 的值。所以,我們可以通過 indexPath 來確定每個 Cell 的唯一性。除了 UITableViewCell 這種情況之外, UIAlertController 也比較特殊。它的特殊性在于視圖層級的不固定,因為它可能出現(xiàn)在任何頁面中。但是,我們都知道它的功能區(qū)分往往通過彈窗內(nèi)容來決定,所以可以通過內(nèi)容來確定它的唯一標識。比如執(zhí)行 insertSubView:atIndex:、removeFromSuperView 等方法時,我們也無法得到唯一標識,即使只截取部分路徑也無法保證后期代碼更新時不會動到這個部分。就算是運行時視圖層級不會修改,以后需求迭代頁面更新頻繁的話,視圖唯一標識也需要同步的更新維護。

7、這種問題就不好解決了,事件唯一標識的準確性難以保障,這也是通過運行時方法替換進行無侵入埋點很難在各個公司全面鋪開的原因??!

8、總結(jié):運行時替換方法進行無侵入埋點的方案。這套方案由于唯一標識難以維護和準確性難以保障的原因,很難被全面采用,一般都只是用于一些功能和視圖穩(wěn)定的地方,手動侵入式埋點方式依然占據(jù)大部分場景。無侵入埋點也是業(yè)界一大難題,目前還只是初級階段,還有很長的路要走。我認為,運行時替換方法的方式也只是一種嘗試,但是現(xiàn)實中業(yè)務代碼太過復雜。同時,為了使無侵入的埋點能夠覆蓋得更全、準確度更高,代價往往是對埋點所需的標識維護成本不斷增大。所以說,我覺得這種方案并不一定是未來的方向。

6、多線程的那些坑

1、第一個坑:常駐線程。盡量不要用!

如果你需要確實需要?;罹€程一段時間的話,可以選擇使用 NSRunLoop 的另外兩個方法 runUntilDate: 和 runMode:beforeDate,來指定線程的?;顣r長。讓線程存活時間可預期,總比讓線程常駐,至少在硬件資源利用率這點上要更加合理。

2、并發(fā) ?是多線程技術的第二個大坑。

總結(jié)來講,類似數(shù)據(jù)庫這種需要頻繁讀寫磁盤操作的任務,盡量使用【串行隊列來管理】,避免因為多線程并發(fā)而出現(xiàn)內(nèi)存問題。

3、內(nèi)存問題

創(chuàng)建線程的過程,需要用到物理內(nèi)存,CPU 也會消耗時間。而且,新建一個線程,系統(tǒng)還需要為這個進程空間分配一定的內(nèi)存作為線程堆棧。堆棧大小是 4KB 的倍數(shù)。在 iOS 開發(fā)中,主線程堆棧大小是 1MB,新創(chuàng)建的子線程堆棧大小是 512KB。

除了內(nèi)存開銷外,線程創(chuàng)建得多了,CPU 在切換線程上下文時,還會更新寄存器,更新寄存器的時候需要尋址,而尋址的過程還會有較大的 CPU 消耗。所以,線程過多時內(nèi)存和 CPU 都會有大量的消耗,從而導致 App 整體性能降低,使得用戶體驗變成差。CPU 和內(nèi)存的使用超出系統(tǒng)限制時,甚至會造成系統(tǒng)強殺。這種情況對用戶和 App 的傷害就更大了。

總結(jié):其實,線程是個非常大的這個話題,涉及的知識也非常多,而這次只是選取了常駐線程和并發(fā)詳細展開。因為,這兩個技術非常容易使用不當,造成不堪設想的后果。

所以,建議是:【常駐線程一定不要濫用,最好不用】。對于多線程并發(fā)也是一樣,【除非是并發(fā)數(shù)量少且可控,或者 必須要在短時間內(nèi)快速處理數(shù)據(jù)的情況】,否則我們在一般情況下【為避免數(shù)量不可控的并發(fā)處理,都需要把并行隊列改成串行隊列來處理】。


7、耗電優(yōu)化

1、獲取電量?

在 iOS 中,IOKit framework 是專門用于跟硬件或內(nèi)核服務通信的。所以,我們可以通過 IOKit framework 來獲取硬件信息,進而獲取到電量消耗信息。在使用 IOKit framework 時,

你需要:首先,把 IOPowerSources.h、IOPSKeys.h 和 IOKit 這三個文件導入到工程中;

然后,把 batteryMonitoringEnabled 置為 true;

最后,通過如下代碼獲取 1% 精確度的電量信息。

獲取耗電量

2、如何診斷電量問題?

回到最開始的問題,當你用排除法將所有功能注釋掉后,如果還有問題,那么這個耗電一定是由其他線程引起的。創(chuàng)建這個耗電線程的地方可能是在其他地方,比如是由第三方庫引起,或者是公司其他團隊開發(fā)的庫。

找到耗電線程
多線程 CPU 使用率檢查的完整代碼

3、優(yōu)化電量

對 CPU 的使用要精打細算,要避免讓 CPU 做多余的事情。對于大量數(shù)據(jù)的復雜計算,應該把數(shù)據(jù)傳到服務器去處理,如果必須要在 App 內(nèi)處理復雜數(shù)據(jù)計算,【可以通過 GCD 的 dispatch_block_create_with_qos_class 方法指定隊列的 Qos 為 QOS_CLASS_UTILITY】,將計算工作放到這個隊列的 block 里。在 QOS_CLASS_UTILITY 這種 Qos 模式下,系統(tǒng)針對大量數(shù)據(jù)的計算,以及復雜數(shù)據(jù)處理專門做了電量優(yōu)化。

4、I / O操作優(yōu)化:

除了 CPU,I/O 操作也是耗電大戶。任何的 I/O 操作,都會破壞掉低功耗狀態(tài)。那么,針對 I/O 操作要怎么優(yōu)化呢?

業(yè)內(nèi)的普遍做法是,【將碎片化的數(shù)據(jù)磁盤存儲操作延后,「先在內(nèi)存中聚合」,然后再進行磁盤存儲】(請求到的數(shù)據(jù)不要立即進行磁盤寫入,先放入內(nèi)存中,然后批量寫入磁盤。)。碎片化的數(shù)據(jù)進行聚合,在內(nèi)存中進行存儲的機制,可以使用系統(tǒng)自帶的 NSCache 來完成。

NSCache 是線程安全的,NSCache 會在到達預設緩存空間值時清理緩存,這時會觸發(fā) cache:willEvictObject: 方法的回調(diào),在這個回調(diào)里就可以對數(shù)據(jù)進行 I/O 操作,達到將聚合的數(shù)據(jù) I/O 延后的目的。I/O 操作的次數(shù)減少了,對電量的消耗也就減少了。

5、蘋果官方耗電優(yōu)化方案:

蘋果公司專門維護了一個電量優(yōu)化指南 鏈接“Energy Efficiency Guide for iOS Apps”,分別從 CPU、設備喚醒、網(wǎng)絡、圖形、動畫、視頻、定位、加速度計、陀螺儀、磁力計、藍牙等多方面因素提出了電量優(yōu)化方面的建議。所以,當使用了蘋果公司的電量優(yōu)化指南里提到的功能時,嚴格按照指南里的最佳實踐去做就能夠保證這些功能不會引起不合理的電量消耗。同時,蘋果公司在 2017 年 WWDC 的 Session 238 也分享了一個關于如何編寫節(jié)能 App 的主題“Writing Energy Efficient Apps”鏈接。

8、RunLoop精講

1、資料:

我建議你按照下面的順序來學習 RunLoop 原理,堅持下來你就會對 RunLoop 的基礎概念掌握得八九不離十了。首先,你可以看一下孫源的一個線下分享《RunLoop》鏈接,對 RunLoop 的整體有個了解。然后,你可以再看官方文檔,全面詳細地了解蘋果公司設計的 RunLoop 機制,以及如何運用 RunLoop 來解決問題。最后,了解了 RunLoop 的機制和運用后,你需要深入了解 RunLoop 的實現(xiàn),掌握 RunLoop 原理中的基礎概念。ibireme 的一篇文章 《深入理解RunLoop》,結(jié)合著底層 CFRunLoop 的源碼,對 RunLoop 機制進行了深入分析。

9、PromiseKit 異步事件鏈編程

1>?Promise 對象會有三種狀態(tài),分別是 pending、fulfilled、rejected:pending 表示 Promise 對象當前正在等待異步事件處理中;fulfilled 指的是 Promise 對象當前處理的異步事件已經(jīng)成功完成;rejected 表示 Promise 對象當前處理的異步事件沒有成功。

2>Promise 對象還有兩個重要的方法,分別是 then 和 catch。Promise 對象每次執(zhí)行完 then 和 catch 方法后,這兩個方法會返回先前的 Promise 對象,同時根據(jù)異步操作結(jié)果改變 Promise 對象的狀態(tài)。

3>執(zhí)行 then 方法后返回的 Promise 對象是 rejected 狀態(tài)的話,程序會直接執(zhí)行 catch 方法。then 方法執(zhí)行的就是訂閱操作,Promise 對象觸發(fā) then 方法就是事件總線中的發(fā)布操作,then 方法執(zhí)行完返回 Promise 對象能夠繼續(xù)同步執(zhí)行多個 then 方法,由此,實現(xiàn)了一個發(fā)布操作對應多個訂閱事件。有了 Promise 對象后,整個異步發(fā)布和訂閱操作都以同步操作的方式表現(xiàn)出來了。Promise 對象不僅能夠避免回調(diào)層層嵌套,而且通過 Promise 的統(tǒng)一接口,使得事件總線的發(fā)布和訂閱操作更加規(guī)范和易用。

4>?多次異步請求通過 Promise 的方法調(diào)用,看起來就像進行同步操作一樣,順序和邏輯也更加清晰了。使用 then 方法可以讓異步操作一個接著一個地按順序進行。如果異步操作 fetchUserInfo 失敗,會返回一個狀態(tài)是 rejected 的 Promise 對象,返回的這個 Promise 對象會跳過后面所有的 then 方法直接執(zhí)行 catch 方法。這就和事件總線中發(fā)布事件觸發(fā)后,訂閱事件會一個接一個執(zhí)行是一樣的。

5>PromiseKit 還為蘋果的 API 提供了擴展。這些擴展需要單獨集成,你可以在PromiseKit 組織頁面獲取。目前大部分常用的 API 都有擴展,比如 UIKit、Foundation、CoreLocation、QuartzCore、CloudKit 等等,甚至還支持了第三方的框架 Alamofire。如果你覺得 PromiseKit 提供的擴展還不夠,還想讓你使用的第三方庫也支持 Promises 的話,可以通過 PromiseKit 提供的擴展文檔,或者直接查看已支持的第三方庫(比如 Alamofire )的擴展實現(xiàn),去學習如何讓其他庫也支持 Promises。

11、富文本渲染:(WebView 、 YYText)

1、WebView

使用 WebView 顯示文章只需要創(chuàng)建一個 UIWebView 對象,進行一些基本滾動相關的設置,然后讀取 HTML 字符串就可以了

和 UIWebView 的 loadRequest 相比,UIWebView 通過 loadHTMLString 直接讀取 HTML 代碼,省去了網(wǎng)絡請求的時間,展示的速度非常快。不過,HTML 里的圖片資源還是需要通過網(wǎng)絡請求來獲取。所以,如果能夠在文章展示之前就緩存下圖片,那么無需等待,就能夠快速完整地展示豐富的文章內(nèi)容了。那么,我應該使用什么方案來緩存文章中的圖片呢?

在 Cocoa 層使用 NSURLProtocol 可以攔截所有 HTTP 的請求,因此我可以利用 NSURLProtocol 來緩存文章中的圖片。

webView加載富文本

13、開發(fā)代碼規(guī)范 (關于好的代碼規(guī)范,接下來我會從常量、變量、屬性、條件語句、循環(huán)語句、函數(shù)、類,以及分類這 8 個方面和你一一說明。)

1、常量:在常量的使用上,我建議你要盡量使用類型常量,而不是使用宏定義。比如,你要定義一個字符串常量,?static NSString * const STMProjectName = @"GCDFetchFeed";

變量名應該可以明確體現(xiàn)出功能,最好再加上類型做后綴。這樣也就明確了每個變量都是做什么的,而不是把一個變量當作不同的值用在不同的地方。

在使用之前,需要先對變量做初始化,并且初始化的地方離使用它的地方越近越好。

不要濫用全局變量,盡量少用它來傳遞值,通過參數(shù)傳值可以減少功能模塊間的耦合。

2、屬性:在 iOS 開發(fā)中,關于屬性的編碼規(guī)范,需要針對開發(fā)語言做區(qū)分:Objective-C 里的屬性,要盡量通過 get 方法來進行懶加載,以避免無用的內(nèi)存占用和多余的計算。Swift 的計算屬性如果是只讀,可以省掉 get 子句。

3、類:在 Objective-C 中,類的頭文件應該盡可能少地引入其他類的頭文件。你可以通過 class 關鍵字來聲明,然后在實現(xiàn)文件里引入需要的其他類的頭文件。對于繼承和遵循協(xié)議的情況,無法避免引入其他類的頭文件,所以你在代碼設計時還是要盡量減少繼承,特別是繼承關系太多時不利于代碼的維護和修改,比如說修改父類時還需要考慮對所有子類的影響,如果評估不全,影響就難以控制。

4、首先,我覺得要利用好 Code Review 這個卡點,先使用靜態(tài)檢查工具對提交的代碼進行一次全面檢查。如果是 Swift 語言的話,你可以使用?SwiftLint工具來檢查代碼規(guī)范。Swift 通過 Hook Clang 和 SourceKit 中 AST 的回調(diào)來檢查源代碼,如何使用 SourceKit 開發(fā)工具可以參看這篇文章“Uncovering SourceKit”。

14、iOS學習資料(高級)

1、完整的例子來系統(tǒng)學習 App 開發(fā),我推薦你查看一下 GitHub 上的Open-Source iOS Apps https://github.com/dkhamsing/open-source-ios-apps項目。作者在這個項目中收錄了很多優(yōu)秀的、完整的開源 iOS App,并做了詳細分類,還專門標出了上架了 App Store 的開源 iOS App。

2、Awesome iOS https://github.com/vsouza/awesome-ios也是一個值得推薦的網(wǎng)站,里面包含了 iOS 開發(fā)的方方面面,而且內(nèi)容都是經(jīng)過人工篩選、分類的。我覺得,你遇到任何 iOS 的問題,都應該先到這個網(wǎng)站看看。Awesome iOS 最大的特點就是大而全,囊括了從開發(fā)、調(diào)試到發(fā)布 App Store 的各種學習資料,也包含了博客、書籍、教程、郵件訂閱、播客的推薦。同時,這個網(wǎng)站還包括了 iOS 開發(fā)更細分的 Awesome 推薦,比如關于 ARKit 的 Awesome ARKit,關于面試問題收集的 Awesome iOS Interview question list 等等。

3、這里有份列表,列出了 iOS 領域那些知名開發(fā)者,你可以通過關注他們的博客、Twitter、GitHub ,來了解走在 iOS 領域前沿開發(fā)者的視野和 iOS 最新的動向。除了關注知名開發(fā)者外,你還可以關注下?開源項目團隊的列表,如果你正在使用他們的開源項目,通過關注他們的動向,隨時了解這些開源項目的最新進展。

4、Raywenderlich出版的圖書質(zhì)量都非常不錯,可以一步一步教你掌握一些開發(fā)知識,內(nèi)容非常實用,而且這些圖書的涉及面廣。比如,這些圖書包括有 ARKit、Swift 服務端的 Vapor 和 Kitura、Metal、數(shù)據(jù)結(jié)構和算法的 Swift 版、設計模式、Core Data、iOS 動畫、Apple 調(diào)試和逆向工程、RxSwift、Realm、2D 和 3D 游戲開發(fā)等各個方面。另外,objc.io家的圖書會從原理和源代碼實現(xiàn)的角度來講解知識點,也非常不錯,內(nèi)容比 Raywenderlich 出版的圖書更深入,適合有一定 iOS 開發(fā)經(jīng)驗的人閱讀。Raywenderlich 和 objc.io 的書基本都是 Swift 來寫的。如果你想更深入地理解 Objective-C 的話,我推薦《Objective-C 高級編程》這本書。這本書里的知識點并不多,主要講的是內(nèi)存管理、Block、GCD(Grand Central Dispatch)。這三個知識點對 Objective-C 來說非常重要,如果使用不當將會置你的工程于風險之中。正是因為涉及的知識點不多,所以全書能基于蘋果公司公開的源碼,集中講清楚這三個知識點。這,非常難得。因此,如果你對內(nèi)存管理、Block、GCD 了解地不是很透徹,我建議你仔細閱讀這本書。如果你想要了解系統(tǒng)工作原理的話,我推薦閱讀《程序員的自我修養(yǎng) - 鏈接、裝載與庫》。這本書詳細且深入地講解了硬件、操作系統(tǒng)、線程的知識。閱讀這本書之前,你需要先掌握 CPU、計算機原理、匯編、編譯原理、C 語言、C++ 語言等計算機學科的基本知識。掌握了這些知識后再閱讀這本書,它能幫你把知識串起來,幫你從代碼編譯、鏈接到運行時內(nèi)存虛擬空間和物理空間映射的角度,了解一個程序從編寫到運行時的底層知識的方方面面?,F(xiàn)在編程技術不斷推陳出新,不斷通過添加中間層將底層埋住,新一代開發(fā)人員也越來越不重視底層知識,所以當他們學到的上層知識被更新替代以后就會感嘆趕不上技術更新的腳步,知識焦慮感越來越嚴重。而讀完這本書,你就會發(fā)現(xiàn),有些知識是不會變的,不管上層如何變化,只要抓住這些知識就能夠抓住核心,掌握技術的走向。《程序員的自我修養(yǎng) - 鏈接、裝載與庫》耗時 30 年才被出版,期間作者不斷優(yōu)化其中的內(nèi)容,最終成為一本經(jīng)典圖書。正如其名,程序員的自我修養(yǎng)。

15、iOS內(nèi)存管理

1> 虛擬內(nèi)存

由于要解決多程序多任務同時運行的這些問題,所以增加了一個中間層來間接訪問物理內(nèi)存,這個中間層就是虛擬內(nèi)存。虛擬內(nèi)存通過映射,可以將虛擬地址轉(zhuǎn)化成物理地址。虛擬內(nèi)存會給每個程序創(chuàng)建一個單獨的執(zhí)行環(huán)境,也就是一個獨立的虛擬空間,這樣每個程序就只能訪問自己的地址空間(Address Space),程序與程序間也就能被安全地隔離開了。

有了虛擬內(nèi)存這樣一個中間層,極大地節(jié)省了物理內(nèi)存。iOS 的共享庫就是利用了這一點,只占用一份物理內(nèi)存,卻能夠在不同應用的多份虛擬內(nèi)存中,去使用同一份共享庫的物理內(nèi)存。

2>?對于在 iOS 開發(fā)過程中如何優(yōu)化內(nèi)存,蘋果公司在 2018 年的 WWDC Session 416: iOS Memory Deep Dive https://developer.apple.com/videos/play/wwdc2018/416/上進行了詳細講解,其中就包含了 iOS 虛擬內(nèi)存機制的變化。

圖片資源不僅是影響 App 包大小的重要因素,也是內(nèi)存的消耗大戶。蘋果公司在 2018 年的 WWDC Session 219: Images and Graphics Best Practices https://developer.apple.com/videos/play/wwdc2018/219/?中,還專門介紹了關于圖片的最佳實踐,并針對減少內(nèi)存消耗進行了詳細講解。

對于 App 處在低內(nèi)存時如何處理,你可以看看這篇文章“No pressure, Mon! Handling low memory conditions in iOS and Mavericks” http://newosxbook.com/articles/MemoryPressure.html。

第三方內(nèi)存檢測工具有 MLeaksFinder https://time.geekbang.org/column/article/98560、FBRetainCycleDetector https://github.com/facebook/FBRetainCycleDetector、OOMDetector https://github.com/Tencent/OOMDetector。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容