筆者由于在iOS開發(fā)過程中做過一些優(yōu)化的工作,對iOS性能優(yōu)化有一些粗淺的認識,一直想把自己這些經(jīng)驗,簡單總結(jié)一下。于是最近在工作閑暇時間,準備針對iOS開發(fā)的性能優(yōu)化寫一系列文章。
作為整個系列的第一篇,我打算針對iOS的優(yōu)化中的一些總體原則做一些總結(jié)。因為我覺得無論列表流暢度優(yōu)化也好、啟動時間優(yōu)化也好還是說其他方面的優(yōu)化,都有一些共性的原則,只有掌握了這些總體性的原則,才能夠更好的做優(yōu)化,給我們具體的優(yōu)化任務指明方向,讓我們少繞彎路。后面如果時間允許,我可能會寫一些關(guān)于列表流暢度、啟動時間和內(nèi)存優(yōu)化等方面的文章。
我對優(yōu)化總體原則總結(jié)出包括不要提前過度優(yōu)化、要找到性能瓶頸、要在不同性能指標間權(quán)衡、要理解優(yōu)化任務的底層運行機制和要有技術(shù)保障體系五大原則,其中具體闡述每一個原則的時候并不局限于性能優(yōu)化方面,會發(fā)散到其他的相關(guān)領(lǐng)域,會對一些延伸的領(lǐng)域做一些簡單的探討,希望能夠?qū)ψx者有一些啟示。以下是主要內(nèi)容。
一、不要提前過度優(yōu)化
這個原則包括優(yōu)化的過程中需要避免的兩個陷阱,即提前優(yōu)化和過度優(yōu)化。
提前優(yōu)化指的是在開發(fā)的起始階段就把性能優(yōu)化作為一個重要的任務來考慮,在沒有實際數(shù)據(jù)指標的基礎(chǔ)上,為了性能提前做的些盲目優(yōu)化工作。
當然這個觀點可能會引起爭議,因為在某些開發(fā)領(lǐng)域,一些性能指標以歷史的經(jīng)驗來說,的確有很大概率甚至必然會有性能瓶頸的問題,因此在架構(gòu)初期就需要考慮性能的問題。 因此如果把“不要提前優(yōu)化“這個觀點推廣所有開發(fā)領(lǐng)域上的話,我認為可能不一定合適。但是如果把此觀點約束在iOS開發(fā)這一領(lǐng)域內(nèi),我個人認為還是成立的。因為在目前階段iOS平臺設備性能普遍較好,蘋果無論是硬件層面還是系統(tǒng)層面對性能方面都做了大量的優(yōu)化。所以我認為性能方面并不是iOS開發(fā)過程中需要首要考慮的因素。相比性能, 我個人認為在iOS開發(fā)的初始階段,以下幾個方面是更重要的,是需要首先考慮的。
首先需要考慮的是架構(gòu)的選擇,這里的架構(gòu)指的是Native架構(gòu)、web架構(gòu)、Native和web混合架構(gòu)和跨平臺的架構(gòu)。這里面我個人的意見是首先應該盡量避免使用web架構(gòu),從Facebook早期的失敗經(jīng)驗可以看出,web和Native相比的確存在諸多性能、體驗等方面的問題。連大廠都無法徹底改善webview的問題,何況我們。但是在一些和用戶體驗相比,對動態(tài)化需求更加迫切的應用場景下,是可以選擇web架構(gòu)的,比如大家都一直在吐槽某鐵路售票軟件。Native架構(gòu)的優(yōu)勢是產(chǎn)品體驗好,對大多數(shù)iOS開發(fā)者技術(shù)棧友好,缺點是由于蘋果對熱更新做了嚴格限制,導致一些動態(tài)化的方案無法使用。Native和web混合架構(gòu)主要是在Native架構(gòu)上,在一些運營需求十分強烈的場景下(如電商等場景),某些模塊使用web開發(fā),這樣既可以在App大部分場景下使用Native架構(gòu),保證用戶體驗,又滿足了部分場景動態(tài)化的需求。跨平臺的架構(gòu)主要是可以減少多端開發(fā)的成本,使用一套代碼完成iOS和Android兩個平臺的開發(fā),目前主流的框架有ReactNative、Weex和Xamarin等。這些跨平臺架構(gòu)的愿景都很美好,但實際使用過程中,個人覺得現(xiàn)階段并不比使用Native架構(gòu)節(jié)省人力,其中會遇到許多已知的未知的坑,當然作為新的技術(shù)我們應該持開發(fā)的心態(tài),但在使用時候也需要全面的評估,尤其作為一個可能有很長生命周期的應用,在使用非官方推薦的開發(fā)架構(gòu)也好、開源庫也好,如果后期無人維護的話,自己團隊是不是有實力去接盤,如果不能,使用蘋果官方推薦的技術(shù)棧則更穩(wěn)妥些。

其次需要考慮的是開發(fā)語言的選擇,這個方面其實在選擇了架構(gòu)之后,也將可選的開發(fā)語言范圍縮小到幾個。而且實際項目開發(fā)中并不難選,因為團隊開發(fā)人員的技術(shù)棧幾乎決定了使用的開發(fā)語言,如果使用了大部分團隊成員都不熟悉的語言,我相信即使所選語言在很多方面都有壓倒性的優(yōu)勢,項目的推進也不會十分順利。但是排除團隊技術(shù)棧的因素,不同的開發(fā)語言的確是各有千秋。大家都知道iOS開發(fā)過程中,如果使用Native架構(gòu),官方的開發(fā)語言是objc和swift。objc是早期iOS開發(fā)的官方推薦語言,優(yōu)點是其動態(tài)性,十分靈活,可以實現(xiàn)許多“黑魔法”,缺點語法略怪異(當然對于iOS開發(fā)者,使用久了也不覺得怪了),另外是對一些高級的語言特性支持的不是很好(記得最初使用objc開發(fā)iOS應用的時候,因為一些特殊的需求。由于objc不支持namespace,給團隊造成了很大的困擾)。swift是蘋果近年來主推的開發(fā)語言, 其吸收了許多其他語言的先進特性,也比較容易上手。關(guān)于兩種開發(fā)語言的具體技術(shù)細節(jié),大家有興趣可以自己查看一些資料了解下。雖然蘋果一直在力推swift,但是目前在國內(nèi)iOS開發(fā)領(lǐng)域,由于一些用戶基數(shù)大的主流App,均是在swift出現(xiàn)前使用objc編寫的,而且大多經(jīng)過了數(shù)年的版本迭代,加之早起swift ABI的不穩(wěn)定和版本之間升級需要較多工作,還有swift和objc混編的一些問題,導致目前國內(nèi)主流App大多仍使用objc作為開發(fā)語言。在一些創(chuàng)業(yè)公司,或者新的項目中,才有部分開發(fā)者使用swift,當然如果目光放長遠的話,未來一定是swift的天下,這兩年objc在每年的語言排名中逐年下降也側(cè)面印證了這一點。 除了官方推薦的objc和是swift之外,如果使用跨平臺等其他架構(gòu),還可以使用如js、c#等語言,有興趣的可以自行了解下。
再次需要考慮的是開發(fā)過程中具體的代碼架構(gòu)的選擇,這里只簡單談談Native架構(gòu)下的代碼架構(gòu)選擇。 目前iOS開發(fā)中常用的架構(gòu)有MVC 、MVVM、VIPER、MVP等。關(guān)于這些架構(gòu),網(wǎng)上目前有很多的介紹,大家如果對具體細節(jié)有興趣可以自行查閱。這里我只想補充一點,大家在學習和實踐時,不要盲目跟風新技術(shù),比如MVVM等架構(gòu)未必比MVC好很多,MVC也未必是一個過時的框架。要知道很多新架構(gòu)帶來的擴展性和解耦行都是通過引入間接層來實現(xiàn)的,隨之而來的可能是更多的膠水代碼和更復雜的代碼結(jié)構(gòu)。希望大家在選擇的時候能夠根據(jù)項目的特點和團隊自身的狀況,選擇最適合自己團隊和項目的代碼架構(gòu)。
除了上面說的三點,還有一些其他的關(guān)鍵點需要大家在項目初期考慮,比如如何在團隊內(nèi)部達成統(tǒng)一的代碼風格?一些關(guān)鍵的技術(shù)如何選型?如何保證代碼結(jié)構(gòu)清晰、簡單、擴展性好等等。性能問題可以在項目后期開始考慮,如果真的發(fā)現(xiàn)明顯的性能問題再優(yōu)化也來得及。比如項目一開始憑直覺感覺某一個模塊可能會有性能問題,就盲目使用多線程,而不是根據(jù)實際情況具體問題具體分析。會導致程序復雜且容易出現(xiàn)線程安全問題。
過度優(yōu)化是指為了優(yōu)化性能,過度增加系統(tǒng)復雜度和維護成本,使得開發(fā)周期變長。雖然可能性能上帶來了一定的提升,但是和過度優(yōu)化而導致的這些缺點來比,這么做顯而易見是得不償失的。
筆者在工作過程中,發(fā)現(xiàn)許多同學在性能優(yōu)化過程中,都容易陷入這種過度優(yōu)化的陷阱。比如這一個簡單的設置界面,一共只有十幾個靜態(tài)的cell, 如果去考慮圓角性能、離屏渲染、圖片緩存、高度緩存、異步渲染甚至緩存布局信息,這些無疑是陷入了過度優(yōu)化的陷阱,在這個應用背景下,簡單快速的實現(xiàn)功能才是第一要務。在目前蘋果的開發(fā)框架和平臺上,一般如果出現(xiàn)性能問題,以我的實際經(jīng)驗來說,問題大部分是出在業(yè)務邏輯上面,所以遇到問題首先需要在業(yè)務邏輯上找問題,一些過度的極限的優(yōu)化,完全是沒有必要的。
其實不光是性能優(yōu)化,我發(fā)現(xiàn)許多同學在日常開發(fā)中,處處都有過度設計的情況。比如設計模式中的design happy這一陷阱,許多初學者在剛開始學習設計模式的時候,十分癡迷設計模式在解決不同問題時,對代碼的解耦性和可擴展性上的威力,在開發(fā)過程中會時時刻刻想著應該用什么設計模式。結(jié)果導致很多的過度設計,其實我們寫代碼過程中,如果能遵守基本的SOLID原則,大部分情況下就可以寫出高質(zhì)量的代碼。
另外一個例子是組件化。近期iOS組件化是一個十分流行的話題,有許多團隊提出了不同的組件化方案。實際項目中,團隊在是否采用組件化方式開發(fā)的選擇上,我希望要結(jié)合項目特點和團隊組織架構(gòu)形式具體問題具體分析,不要盲目跟風。在產(chǎn)品功能相對單一、開發(fā)人員較少、并行開發(fā)需求不強烈的情況下,推行組件化,不但增加系統(tǒng)復雜度,而且增加開發(fā)人員學習成本高,使得開發(fā)成本變大,我個人覺得這種規(guī)模的應用初期需要更多考慮的是如何快速上線、快速迭代和保證App質(zhì)量。因此如果能夠進行清晰的分層,嚴格遵守簡單統(tǒng)一的架構(gòu)模式即可。組件化比較適合從功能形態(tài)上可以清晰劃分若干模塊的產(chǎn)品,比如美團、58同城、淘寶和攜程等產(chǎn)品,內(nèi)部有多個業(yè)務模塊,而且這些公司開發(fā)此類“航母”App的時候,會從組織架構(gòu)把不同業(yè)務劃分給不同的開發(fā)團隊,為了能夠保證不同團隊之間能夠獨立并行開發(fā)和發(fā)版,最大程度上減少代碼的依賴程度,這個時候應用組件化則是最佳實踐。
上面是對不要提前過度優(yōu)化原則的詳細闡述,并引申到相關(guān)開發(fā)領(lǐng)域,做了一些不成熟的探討。下面介紹性能優(yōu)化總體原則的第二個。

二、要找到性能瓶頸
在做優(yōu)化前,一定要首先找到性能瓶頸有哪些,依性能嚴重程度逐個解決。不要盲目優(yōu)化,否則最后可能花了很大的力氣,優(yōu)化掉的可能只是性能損耗很小的一部分。這一原則我覺得尤為重要,因為我在工作中遇見過包括我在內(nèi),很多不進行性能瓶頸查找,全憑主觀猜測進行性能優(yōu)化的情況。 在尋找性能瓶頸過程中,也需要注意以下問題。
不要主觀猜測,讓性能評測數(shù)據(jù)說話。
這一點十分重要,要時刻記住要以事實說話,不要以為某個函數(shù)使用的算法的時間復雜度是O(n2)就覺得一定會有性能問題,非要費很大力氣優(yōu)化到O(n*logn), 殊不知你的輸入數(shù)據(jù)可能只有幾十或者幾百個,即使O(n2)也不會有多大的性能問題。也不要以為某個方法僅僅調(diào)用了系統(tǒng)庫的一個簡單get方法,就不會有什么性能問題,殊不知這個get方法里可能包含一些十分耗時的操作(比如磁盤IO)。因此在遇到性能問題的時候,一定不要憑主觀猜測,實地跑一下性能數(shù)據(jù),讓數(shù)據(jù)告訴我們性能瓶頸究竟在哪里。
要使用恰當?shù)男阅茉u測工具。
對于開發(fā)版本的性能優(yōu)化,Xcode提供的instruments絕對是最好的尋找性能瓶頸的工具,沒有之一。instruments有豐富的性能評測工具,包括常用的Core Animation、Time Profiler、Leaks和Allocations等等。這些工具在分析函數(shù)執(zhí)行時間、fps和內(nèi)存等方面給我提供了十分便捷的功能。在使用instruments過程中需要注意:
要使用真機,而不是模擬器。模擬器的CPU比iOS機器要快很多,所以在模擬器上,CPU相關(guān)的操作會更快。因為Mac的GPU和iOS設備上的GPU不同,所以模擬器需要在CPU上通過軟件去模擬iOS設備上的GPU,所以GPU相關(guān)的操作會更慢。因此如果使用模擬器去進行性能優(yōu)化的話,評測設備和真實用戶設備性能表現(xiàn)的不一致,會導致優(yōu)化的效果大打折扣。這里面內(nèi)存是一個例外,在做內(nèi)存優(yōu)化的時候,使用模擬器和真機一般差別不大,可以使用模擬器進行內(nèi)存的優(yōu)化。
要使用Release配置而不是Debug配置,因為在release包的時候,編譯器會做一些優(yōu)化以提高性能。自己的工程代碼可能也會在release下做一些優(yōu)化,比如去除log信息和一些debug功能等。我們關(guān)心的是release下的性能,因為用戶最終也是使用的release的安裝包。所以測試的時候要一定要記住在release配置下進行,instruments進行性能評測的時候,默認是在release下進行的。但是工程代碼里面的優(yōu)化則需要自己注意。筆者就曾經(jīng)為此付出過很大代價,因為沒有注意工程代碼里面的一些debug功能,導致優(yōu)化過程中錯誤的認為動態(tài)庫是影響啟動時間的罪魁禍首,花了很大力氣把動態(tài)庫修改為靜態(tài)庫,白白浪費了很多時間。
要使用性能相對差的機器進行評測,因為我們需要保證的是我們的應用在性能差的機器上也有良好的表現(xiàn)。如果有條件,最好能夠覆蓋多個機型,和我們傳統(tǒng)上的認識不同,機型越新性能不一定越高,例如iPad3在動畫和渲染性能上比iPad2差。
要覆蓋不同系統(tǒng)版本,因為在iOS系統(tǒng)上,一般系統(tǒng)版本越高,同一機器性能大體上趨于差,所以如果只覆蓋低版本的機型,可能在高版本上表現(xiàn)的性能會不盡如人意。
除了使用instruments,還可以使用log等方式進行查找性能瓶頸。
對于線上的App,查找性能瓶頸的工具主要是Application Performance Management(APM)。目前各大公司基本都有自己的APM,主要是對線上App進行性能監(jiān)控以及預警。因為在開發(fā)和測試階段,由于使用人數(shù)相對比較有限,很難覆蓋所有的業(yè)務場景。而在App發(fā)布出去后,大量用戶在使用過程中的性能表現(xiàn),會給我們的App帶來更全面的性能評測數(shù)據(jù),因此線上App的性能監(jiān)控是十分重要的。
要抓重點,有的放矢。找到性能損耗大的前N個問題,依重要程度和解決的難易程度解決,這樣才能花最少的精力,解決最大問題。
三、要在不同性能指標間權(quán)衡,達到總體最優(yōu)
在已經(jīng)找到性能瓶頸的時候,解決性能問題的方法則需要具體問題具體分析,要在不同性能指標間權(quán)衡,以達到總體最優(yōu)。
這需要我們要有整體的意識,App的性能可以分為很多類,不同的性能指標對用戶體驗造成的影響也不盡相同,比如fps主要影響的是用戶的滑動體驗,頁面加載時間和應用啟動時間影響的是用戶等待時間上的體驗。我們在優(yōu)化的過程中,要牢記我們的目標是希望App的整體體驗最優(yōu),而不是某一單項的性能指標最優(yōu)。不同優(yōu)化指標之間可能是呈正相關(guān),比如優(yōu)化了滑動過程中大量函數(shù)的耗時時間,使得fps性能提升,可能會導致App的耗電量變少。同時,不同優(yōu)化指標也可能是負相關(guān)、相互制約的,比如為了流暢性做了過多的cache,會導致內(nèi)存性能下降,甚至導致因為memory warning導致被系統(tǒng)kill掉,這無疑對App的整體體驗造成了負面的影響。因此實際優(yōu)化過程中需要我們反復權(quán)衡利弊和取舍,達到整體的性能最優(yōu)

四、要理解優(yōu)化任務的底層運行機制
如果不理解優(yōu)化任務的底層運行機制,可能很難達到更好的優(yōu)化效果。
比如在做啟動時間優(yōu)化的時候,如果你不知道iOS中App的啟動時間是由main之前和main之后兩部分時間組成的,此時如果你的App是因為main函數(shù)之前的部分占用了過多的啟動時間,可能你花了大量的精力去優(yōu)化main之后的時間卻沒有達到好的優(yōu)化效果。如果你不知道App啟動過程的運行機制,就無法知道去檢查是否鏈接了過多的自定義的動態(tài)庫或者去load函數(shù)里面確認是否有耗時的操作等等。還有在做fps優(yōu)化的時候,如果不了解卡頓的底層原因是什么、一個view從創(chuàng)建到顯示過程中經(jīng)歷那些步驟、CPU和GPU在這個過程中都扮演什么角色,則很難做到絲滑般的順暢?還有在做內(nèi)存優(yōu)化的時候,如果不了解內(nèi)存分為哪幾類、系統(tǒng)對App和不同類型extension的內(nèi)存限制機制的不同、超過限制系統(tǒng)會采取什么操作等等,也很難把內(nèi)存優(yōu)化做好。因此只有深入了解底層機制才能更好的有針對性的提出更優(yōu)的解決方案。
其實不只是在性能優(yōu)化方面,在開發(fā)過程中很多情況下,了解底層的原理會讓你變得更高效,更容易解決遇到的各種問題。在這里分享一個我印象比較深的一次經(jīng)歷。記得有一次在開發(fā)某個功能的時候,需要用到level db數(shù)據(jù)庫,在開發(fā)過程中做單元測試的時候發(fā)現(xiàn),level db的本地存儲文件在不斷刪除和寫入的過程中,越變越大,甚至達到1G大小。當時第一印象以為是在使用上出了問題,所以上層業(yè)務邏輯上查找問題,結(jié)果查了很久都沒有找到問題。但如果我在使用level db的時候去多了解一下其底層的實現(xiàn)原理,了解LSM(Log-Structured-Merge Tree)的原理,遇到這個問題的時候就不會認為這是一個bug,也不會浪費了大把的時間來做無用功。所以建議大家不要抱怨每天的工作過于簡單枯燥,在開發(fā)過程中多去挖掘一些深層次的東西,不但讓你的技術(shù)的深度不斷加深,也會對你的編碼效率有很大的提升。
五、要有技術(shù)保障體系
性能優(yōu)化不能一勞永逸,我個人覺得更是一場持久戰(zhàn)。不僅需要你能夠在某個特定時期做專項優(yōu)化的攻堅戰(zhàn),還要為打好持久戰(zhàn)做出好的后勤補給,為了能使App長期保持好的性能,不僅僅需要開發(fā)人員有良好的開發(fā)技能,還需要有一些技術(shù)保障和體系。下面簡單羅列我能想到的幾個方面。
要有好的測試保障,這里的測試保障不僅僅指的是測試人員的手動測試,更需要的自動化測試。要建立針對不同性能指標的專項自動化測試,建立一套從定時運行測試到測試結(jié)果的輸出等一套完整的自動化測試體系,能夠為性能的保證提供堅實的數(shù)據(jù)支撐。
引入關(guān)鍵性能指標上線準入制度。在開發(fā)階段,為了不將有性能問題的代碼帶到線上,可以將比如啟動時間、FPS、安裝包大小等指標作為關(guān)鍵指標,上線前進行自動化測試,如指標不達標,不允許上線。
對于線上App使用APM進行監(jiān)控,發(fā)現(xiàn)線上的性能問題,有及時的預警機制,能夠隨時解決線上的性能問題。
開發(fā)過程中,代碼中需要有對性能保障的設計。 比如可以設計可復用的高性能控件,這樣其他開發(fā)人員在開發(fā)類似功能時,可以簡單復用,不僅提升了性能,而且大大節(jié)省了開發(fā)的時間。還有比如為了防止App隨著版本迭代導致啟動時加載的服務越來約多,導致啟動變慢,可以設計App啟動器,把這些任務統(tǒng)一放到主界面加載完成后再執(zhí)行,并且在組內(nèi)開發(fā)人員中形成硬性的規(guī)范,但凡啟動時期不必須的服務,要么不要執(zhí)行,要么統(tǒng)一放到啟動器的主界面加載完成的回調(diào)中執(zhí)行。
定期做一些性能優(yōu)化方面的技術(shù)分享,不僅僅可以提高組內(nèi)同學的開發(fā)技能,還可以活躍組內(nèi)的技術(shù)氣氛。
以上我對iOS性能優(yōu)化的總體原則做的總結(jié),希望能夠?qū)Υ蠹矣幸稽c點啟示。其中可能很多想法并不成熟,也希望大家能夠多多批評互相探討,共同進步。