小公司iOS客戶端代碼優(yōu)化之路:模塊化,組件化,動態(tài)化

疫情基本要結(jié)束了,此間有時間,我總結(jié)了一下,近兩年來我們在iOS客戶端上的代碼優(yōu)化歷程。我們先后經(jīng)歷了模塊化,組件化,動態(tài)化實戰(zhàn)演進(jìn)。本文總結(jié)一下整體思路與所遇到的坑。

先說一說,小公司何時優(yōu)化代碼,為什么進(jìn)行代碼重構(gòu)?

代碼深層的重構(gòu),傷筋動骨,在業(yè)務(wù)高速飛速發(fā)展時,需要兼顧線上代碼穩(wěn)定性,同時進(jìn)行有效的迭代,對于小公司來說注重業(yè)務(wù)的發(fā)展,所以前期很可能沒有資源,做架構(gòu)上升級。另一方面,一個項目如果一個人維護(hù)甚至不需要做架構(gòu),因為整個代碼即使很亂,但是只要在架構(gòu)者頭腦范圍之內(nèi)或者說此時業(yè)務(wù)還不足夠復(fù)雜到創(chuàng)作者都不能掌握,系統(tǒng)都是穩(wěn)定的,只管快速向前跑就行。而一旦兩個人以上維護(hù)一個項目,相互交叉開發(fā)時,維護(hù)者如果不能梳理清架構(gòu)者整體的思路,亦或代碼質(zhì)量不高等原因,就會出現(xiàn)bug頻現(xiàn)的情況,而一旦出現(xiàn)bug頻現(xiàn)的情況,那就說明系統(tǒng)的可維護(hù)依然很差,維護(hù)者無法分清改動一行代碼的影響范圍。進(jìn)一步,維護(hù)者為了保證系統(tǒng)的穩(wěn)定性和自身的安全,依然不敢對項目進(jìn)行徹底優(yōu)化,只能進(jìn)行if else搶救性開發(fā)時,導(dǎo)致系統(tǒng)進(jìn)一步的臃腫。一旦出現(xiàn)這種情況,說明系統(tǒng)該重構(gòu)了,代碼架構(gòu)依然阻礙了系統(tǒng)穩(wěn)定性,進(jìn)一步影響了業(yè)務(wù)的發(fā)展。這就是代碼重構(gòu)的一個重要原因,代碼無法適應(yīng)業(yè)務(wù)的發(fā)展。而本文所講的模塊化或組件化是降低代碼復(fù)雜度的有效辦法,讓業(yè)務(wù)代碼分塊隔離松耦合,分而治之,這樣開發(fā)人員面對的代碼邏輯會有效降低。

這是未改造前的示例簡圖:


1.png

第一步:實現(xiàn)模塊化

模塊化,這個適合做代碼改造的開始,這個是自上而下進(jìn)行的,整體下來投入少,收益大。也是小公司最應(yīng)該實踐的。

首先從業(yè)務(wù)層面考慮,涉及哪些相對獨立的業(yè)務(wù)模塊,按大的模塊進(jìn)行劃分。比方我們的APP的首頁模塊,個人中心模塊,登錄模塊,信用模塊,下單模塊,還款模塊,商城模塊,會員中心都是相對獨立的模塊。我們把這些模塊的引用加入路由中心,讓模塊都依賴路由中心,不要依賴彼此。

這是模塊化之后的示例簡圖:


2.png
業(yè)務(wù)上的收益:
  1. 實現(xiàn)運營方利用通知或短信,通過scheme,打開App,并打開指定的原生頁面或web頁面。
  2. 進(jìn)一步拓展可以實現(xiàn)更多頁面內(nèi)容的跳轉(zhuǎn),后端配置化。
  3. 增強h5與原生的深度交互能力,提升h5的更多價值。
技術(shù)上的收益:
  1. 一個模塊的代碼的進(jìn)來放在一起,減少與其他模塊的引用,加強代碼的內(nèi)聚性。最主要解決的問題防止出現(xiàn)改一個模塊的一處代碼,引發(fā)其他模塊的多個bug,目的上減少修改代碼的影響范圍。
  2. 底層的代碼進(jìn)行抽取,相互沒有任何依賴關(guān)系的庫抽離出來,如工具方法,類拓展,公共UI組件,為組件化做了準(zhǔn)備。
  3. 重要模塊的跳轉(zhuǎn)以及功能都統(tǒng)一由路由中心來管理
  4. 原生頁面遇到問題,可以降維到h5頁面,保障業(yè)務(wù)順利進(jìn)行;拓展了與hybird的交互,實現(xiàn)支持h5與原生的混合跳轉(zhuǎn),兩者導(dǎo)航欄的任意回退,提高了hybird的能力。
路由中心其主要承擔(dān)三部分功能:
  1. 普通的url,直接push并在webview中打開。
  2. 頁面的跳轉(zhuǎn),嗅探當(dāng)前的導(dǎo)航欄,進(jìn)行push或present下一個頁面。和1一起主要方便服務(wù)端動態(tài)配置。
  3. 調(diào)用獨立功能,比如清空當(dāng)前導(dǎo)航棧到首頁,跳轉(zhuǎn)到導(dǎo)航棧中的指定頁面,跳轉(zhuǎn)到指定tab,撥打電話,跳出APP到Appstore,等等,還有一些我們自己封裝的一些和業(yè)務(wù)無關(guān)的功能。這部分功能不少是為了h5準(zhǔn)備的。
  4. 后端接口上,配合自定義路由進(jìn)行升級,同時將頁面信息進(jìn)一步配置化,從頁面跳轉(zhuǎn)到各種文案,圖片都讓服務(wù)端下發(fā),客戶端做好緩存策略,優(yōu)化用戶體驗。

后期維護(hù)者也比較方便,比如增加兩個路由,wzry://app.wangzherongyao.com/laofuziwzry://app.wangzherongyao.com/good_test,只需要兩步走:

  1. 路由表的plist文件中增加url和對應(yīng)的target/aciton
3.png
  1. 在target對應(yīng)的類中,根據(jù)action增加對應(yīng)的方法即可。
4.png
  1. 在其他控制器中調(diào)用
result1.png

路由上的核心實現(xiàn)大家可以參考CTMediator的target-action模式

  1. 根據(jù)CTMediator的架構(gòu),一個組件需要建一個分類并制作私有pod。由于這個階段我們業(yè)務(wù)相對簡單,人員少。我們直接將url映射到一個類名和方法,runtime直接執(zhí)行類對應(yīng)的方法。
  2. 另外我們的路由,綜合了更多的功能,支持分發(fā)普通的url,接管了頁面的跳轉(zhuǎn)行為,加強和了h5的交互

第二步:實現(xiàn)組件化

經(jīng)過模塊化,我們的代碼規(guī)整了不少,穩(wěn)定性也好多了。后來有時間我們又進(jìn)行了組件化。

組件化,對業(yè)務(wù)上的收益來說可以利用寫好的中低層組件,更快的搭建其他功能類似的APP。技術(shù)上的收益來說,首先代碼上的進(jìn)一步物理上的隔離,每個模塊都放在一個倉儲中,搭建私有pod來統(tǒng)一管理,讓模塊與模塊之間的依賴,實現(xiàn)真正意義上的物理解耦,代碼穩(wěn)定性更加穩(wěn)定。其次獨立功能封裝成了獨立組件,更方便復(fù)用,代碼在pod中,就不用再使用copy文件的方式了,并且依賴什么的都不用care了,一個pod install就搞定。我的經(jīng)驗此時最好從下而上的進(jìn)行,因為越底層模塊,越要求穩(wěn)定性,一個底層模塊不兼容性接口的修改,可能涉及很多上層組件都要修改,特別涉及多個APP的時候。

具體實現(xiàn)上來說,在控制對業(yè)務(wù)影響的前提下,我們主要做了兩件事,一個就是路由的升級;另一個是組件的分層與粒度的劃分,其中顆粒度的劃分,根據(jù)具體的實際情況來拆分,并且隨著業(yè)務(wù)的發(fā)展可能分久必合,合久必分,主要說下分層,分層相對更有參考價值。

組件的分層來說:
  • 第一層:第一步的模塊業(yè)務(wù)組件,模塊化中涉及的獨立模塊。
  • 第二層:業(yè)務(wù)基礎(chǔ)組件,這塊的組件主要用于支持上層業(yè)務(wù)組件,里面包含和業(yè)務(wù)相關(guān)性較強,如用戶模塊,日志模塊,升級模塊,web模塊,人臉識別等
  • 第三層:功能組件,和業(yè)務(wù)沒有強聯(lián)系,如網(wǎng)路請求組件,Debug組件,主題配置組件,設(shè)備信息組件等
  • 第四層:基礎(chǔ)組件,最純粹的無相互依賴的組件,主要是對第三方庫和工具類。此處說一下,由于組件化后,大家對于組件會涉及很多pod install,update等,遇到網(wǎng)絡(luò)弱的情況,比較耽誤時間。因為公司沒有g(shù)ithub的鏡像或cache,我們依賴不是很多,就采用了最笨的方法,大家每人幾個把庫搬到公司自己的gitlab上,搬得過程中使用mirror也很方便。

下圖為組件化分層,示例簡圖:


5.png
路由的升級

我們結(jié)合模塊化中的經(jīng)驗對路由進(jìn)行了標(biāo)準(zhǔn)化升級。搭建了面向協(xié)議的路由中心,組件對外暴漏的方法action,都抽像為協(xié)議。這樣做的一方面是實現(xiàn)對組件方法的明晰和規(guī)范,各個組件的所有協(xié)議可以放到一起便于查閱,使用pod進(jìn)行統(tǒng)一的管理,大家對組件依賴這個協(xié)議庫就好,按照設(shè)計的原則,高層模塊不應(yīng)該直接依賴底層模塊,而應(yīng)該依賴兩者的接口。另一方面內(nèi)部組件的調(diào)用可以直接使用協(xié)議的方式,防止了沒必要的url解析過程;并且內(nèi)部組件調(diào)用更多,如果每個調(diào)用方法都設(shè)置url,會產(chǎn)生很多url,url本身可讀性不強,有不少局限性。所以最后我們使用protocal的方式。

具體實現(xiàn)上來說,以前是直接通過路由表,直接找到target-acion并執(zhí)行,組件只需要實現(xiàn)一個方法就好了;現(xiàn)在來說,下面的組件要實現(xiàn)一個協(xié)議方法,并且要提前通過load方法將自己的類信息和滿足的協(xié)議注冊到router中。

現(xiàn)在來看看具體的添加過程:

1.路由表為兼容以前的target-action,在老的plist文件中增加一個protocal鍵值對。新的直接使用省去target鍵值對。

6.png

2.定義一個協(xié)議

7.png

3.實現(xiàn)協(xié)議,并掛載到路由中心

8.png

9.png

4.具體調(diào)用,可以使用url方式,也可以使用協(xié)議的方式

10.png

第三步:實現(xiàn)動態(tài)化

經(jīng)過組件化后,整個項目從上到下,已經(jīng)處于相對松耦合的狀態(tài)。此時我們正好業(yè)務(wù)上需要動態(tài)化。動態(tài)化業(yè)務(wù)上收益在于無需等待發(fā)版就可以更新APP;技術(shù)上,雖然我們通過testflight實現(xiàn)了代碼的灰度測試,但是有些問題是大量用戶才能暴露的問題,這時隨時修復(fù)線上bug的功能就非常有必要了。我們選擇了使用reactnative,性能比hybird的方式好一些。而在組件化的基礎(chǔ)上,對單個模塊進(jìn)行動態(tài)化,對整個項目的影響降到了最小。當(dāng)然也要付出代價拿出資源來踩坑,解決技術(shù)上的問題。新啟一個以RN主導(dǎo)的項目相對好一些。但是如果涉及混合開發(fā),特別是在原有項目中,增加RN模塊的這種情況,就會遇到一些問題。我們的路徑是先拿一個全新的項目的用RN來做,后面再加到現(xiàn)有穩(wěn)定項目中。我們也在探索的階段,就簡單說遇到的問題與解決辦法。

  1. 剛開始的時候,根據(jù)RN腳手架構(gòu)建出的項目,不支持cocopod安裝依賴,使用pod方式安裝組件的時候,就遇到各種編譯錯誤。所幸的是0.60.0后,rn支持了pod方式的注入,0.61.0后更是支持use_framework,方便了很多。
  2. 混合開發(fā)時,第一個是網(wǎng)絡(luò)請求的問題,使用RN來發(fā)送還是原生發(fā)送,最后我們選擇原生發(fā)送。一方面我們測試使用原生頁面發(fā)送請求比RN發(fā)送請求請求,速度快很多;另一方面,因為RN只是整個項目中某一個模塊的一部分,使用一套底層的網(wǎng)絡(luò)框架,如用戶狀態(tài),數(shù)據(jù)解析都不單獨處理。這樣RN頁面只需要處理頁面相關(guān)的邏輯即可。
  3. 混合開始時需要原生頁面與RN頁面的相互跳轉(zhuǎn),這個時候就需要考慮RN頁面承載的問題,我們采用類咸魚的flutter_boost的方案,即在原生頁面打開一個rn頁面時,使用一個viewcontroller作為rn容器,rn頁面的跳轉(zhuǎn)都在這個容器內(nèi)進(jìn)行。
  4. 打包與升級更新的問題。打包系統(tǒng)需要進(jìn)行升級支持RN的打包,同時升級時為應(yīng)對動態(tài)的大小與安全問題,需要進(jìn)行了diff運算,保證下發(fā)的最小的包,同時支持全包更新與回滾。

RN總體使用下來,最好還是和原生搭配使用,感覺性能還是其發(fā)展的瓶頸,js的單線程不能高效的處理并發(fā)問題,同樣一個請求,我們用原生發(fā)送請求回傳給RN頁面,比使用RN自己請求回來,要快10倍以上。另外在混合開發(fā)時,會遇到很多莫名奇妙的問題,需要不斷的填坑,這可能也是跨平臺的代價吧。

總結(jié)

我們開始的模塊化就是在業(yè)務(wù)邏輯越復(fù)雜,代碼耦合嚴(yán)重,bug不斷增多的情況下,進(jìn)行了代碼的重構(gòu)。后面的組件化時是我們有足夠時間情況下,進(jìn)行的代碼預(yù)先升級,為后面的業(yè)務(wù)做準(zhǔn)備。當(dāng)然如果一個項目一開始就以整潔的代碼要求自己,可能后面不需要傷筋動骨的改造;并且代碼的優(yōu)化是有各個維度的,任何時候都可以開始,只要你是有心人??傊軜?gòu)上之改進(jìn),即可以是業(yè)務(wù)受影響,技術(shù)上更新?lián)Q代的戰(zhàn)略反擊;也可以是主動出擊,占領(lǐng)優(yōu)勢地位,為后續(xù)更大的戰(zhàn)斗創(chuàng)造機會的提前布局,預(yù)則立不預(yù)則廢。我感覺小公司至少要實現(xiàn)模塊化,一是業(yè)務(wù)上的需要,二是為組件化做準(zhǔn)備。不必操之過急的去實現(xiàn)組件化,因為組件化之后,必然需要一些額為的維護(hù)工作,還需要一些自動化的配套設(shè)施來輔助才好,比如我們搞了組件的驗證與上傳腳本,打包發(fā)版本自動化構(gòu)建。中大公司自然要會去做組件化,團(tuán)隊人比較多,代碼耦合越來越嚴(yán)重,協(xié)作的時間遠(yuǎn)大于組件化維護(hù)的時間,這個時候組件化可以顯著提升業(yè)務(wù)上開發(fā)效率和穩(wěn)定性。至于動態(tài)化更是看業(yè)務(wù)需要,綜合考慮性能等各種因素,來統(tǒng)一規(guī)劃。

參考文章:

手機淘寶客戶端架構(gòu)探索實踐

iOS應(yīng)用架構(gòu)談 組件化方案

蘑菇街 App 的組件化之路

模塊化與解耦

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

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

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