寫在前面
關於App架構、元件化,本文的內容不會涉及到具體程式碼層面,也不會介紹怎樣使用Cocoapods去做元件化;而是站在軟體工程的角度上,結合自己多年一線開發經驗,去分析如何做App架構,如何通盤考慮什麼樣的架構才是合理的,契合自身業務的,以及架構落地過程中應該規避哪些問題。
名詞解釋:本文中所提到的架構不是實際工程中程式碼架構(MVC、MVVM、MVP),確切的說是一種應用分層架構。而MVC、MVVM、MVP本質是一種軟體架構模式,是App實現過程中的一種編碼模式或者編碼規範。
iOS系統架構回顧
如上圖所示,經典的iOS系統架構分為四層,自下而上分為核心作業系統層、核心服務層、媒體層、使用者互動層。
- Cocoa Touch:提供與使用者互動的相關能力,包括觸控等,最常用的UIKit庫就在該層;除此之外還有MapKit、Address BookUI、PhotosUI等。
- Media Layer:提供圖形與音視訊相關功能的介面,例如Core Animation、OpenGL ES等。
- Core Services:最常用的有Core Foundation、Foundation、CFNetwork、CoreData等。提供最基礎的能力,比如陣列、字典、HashMap、套接字等基礎資料結構。
- Core OS:包含Mach、Kernel、BSD、Socket以及Sandbox等,它主要提供了底層操作的介面,對於應用開發來講直接用到的不是很多。
都說iOS系統牛逼,牛逼在哪?牛逼就牛逼在它有合理的架構分層,還有合理的Api設計,讓你能夠躺著就能做iOS開發,暢享絲滑!它牛逼的檔案管理和檔案隔離機制讓你不需要過多考慮iOS系統安全性問題,逆向開發除外,因為它總是Bug般的存在?。
Q:iOS系統架構這四層之間是如何進行通訊和互動的,是否合理?
A:直接引用標頭檔案,呼叫下層提供的Api進行互動。關於是否合理,我想說的是隻要Api設計的足夠合理,足夠能應對未來一段時間內SDK內部可能的變動,或者說SDK本身是一個很基礎的庫,比如Foundation庫等,我覺得直接引入標頭檔案無傷大雅,具體的我們稍後再討論。
設計一個合理的應用分層架構
麻雀雖小五臟俱全,要想展翅高飛,每個環節缺一不可。
關於如何設計一個合理的應用分層架構,這裡我們拿蓋樓這件事做比喻,筆者幹過建築搬過磚,所以對於蓋樓流程相對來說比較熟悉。
-
第一步:打地基、支模板、澆灌水泥搭架子、搬磚壘牆,這是一切的基礎,高樓要屹立不倒,需要這些模組的長久有力的支撐才行。抽象到應用架構裡面,我們稱之為基礎模組,其主要提供應用最基礎的能力。
-
第二步:鋪地面、造門,其中門在臥室、餐廳都可能會用到。抽象到應用架構裡面,我們稱之為公共業務模組,它主要提供了一些通用的業務模組或者通用的元件。
-
第三步:給大樓賦能,臥室、餐廳、洗漱間等一應俱全,有了這些才能真正體現蓋大樓的意義。臥室等功能都要用到磚、牆、門等基礎模組。在應用架構中,我們把臥室、廚房、洗漱等獨立功能抽象為普通業務模組,每個業務模組都代表一個具體的功能,業務模組間沒有強關聯關係。
Q:除了以上的部分,是否還缺點什麼東西?
A:樓層跟樓層之間需要電梯連線通訊,臥室和廚房之間也需要通道進行連線。同樣對於應用來講,模組間的通訊也需要一個媒介連線起來,我們稱之為匯流排(Bus)。後續會詳細介紹如何實現一個匯流排,讓你的模組各自分工,且模組間的通訊暢通無阻。
經過分析梳理,我們很容易能夠畫出如下的應用架構圖,圖中每層都標出了該層大致包含哪些內容。
圖中,我們按照“蓋大樓”的思路,進一步抽象羅列出了一個App應該包含哪些結構。
應用架構實施落地
在iOS平臺中,我們一般會通過Cocoapods去管理、整合自己的元件。按照工廠化生產App的理念,結合Cocoapods我畫出瞭如下的App整合圖。
- 基礎模組:因模組高度獨立,且高頻使用,若公司內部有多個App同時需要依賴,建議單獨建立私有庫Specs。
- 公共業務模組:功能相對獨立,根據業務需求來決定是否單獨建立私有庫Specs。
- Cocoapods公有庫:所有公司內部App,強烈建議不要直接引入公有Specs。這樣做有兩點好處:
1.跟外部環境有效隔離,第三方庫發生問題,公司內部可控。
2.公有庫太大,每次repo update耗時太長,國內的環境你懂的,沒有科學上網,至少一個小時過去repo也未必更新完畢。所以通用的方案是,若公司內部引用了第三方庫,按照依賴倒置的原則,建議封裝一層之後放到Basic Specs供業務方使用。
又來到了一年一度的QA環節。
Q:如何把握元件拆分粒度?
A:沒有一個可衡量的標準,需要結合具體業務場景,那些複用性高、功能相對獨立就可以考慮做拆分。還有需要注意的是,元件拆分不一定要抽離成pod庫,可以將有一定特性的一組通用元件打成一個pod庫(姑且定義成CommonUIKit)。我們知道pod庫最終都是生成靜態庫引用到主工程的,也就是最終都會經過連結的過程,pod庫過多會帶來一定的App啟動效能開銷,其次pod庫過多也會導致pod管理混亂的問題。
Q:比如一個很小的功能,就一個彈框我需要去做解耦麼,我抽成pod庫別人直接引用不就得了?
A:在回答之前,我們先思考兩個問題。彈框元件未來變動可能性有多大?你設計的Api是否合理,是否能夠滿足未來產品的需求?第二個問題,解耦帶來的益處能夠cover住這些可能的變動帶來的弊端?想清楚這兩個問題,我們就知道設計一個元件是否需要做解耦,是否需要用中間服務去除依賴了。
解決橫向依賴
- 通用元件層的橫向依賴。
通過上圖可以發現,首頁元件實際只是獲取了登入態,但登入模組沒有提供對應服務,則只能通過引用標頭檔案的方式把該元件import進來,兩者耦合在一起。
利用中介軟體的概念,我們可以在兩個模組之間建立一個服務層,專門用來進行模組間的資料通訊,或者非介面跳轉的小粒度元件的資料通訊。這樣就很好的解決了兩個元件的橫向依賴問題。
- 業務模組間的橫向依賴。
這裡主要說的是那些業務功能獨立、業務線之間的橫向依賴。舉例說明,首頁模組可能帶有業務A、業務B、業務C的入口,如果沒有做元件化,則首頁模組連同A、B、C業務都耦合在一起。這裡推薦幾個比較比較常用的路由解決方案。
JLRoutes-URL routing library for iOS with a simple block-based API。
BeeHive-iOS的App模組化程式設計的框架實現方案,吸收了Spring框架Service的理念來實現模組間的API耦合。
CTMediator-基於Mediator模式和Target-Action模式。
Q:我該如何設計一個路由,用於模組間的跳轉?
A:設計路由需要遵循幾個原則。
第一,便於整合,最小的改動即可實現一個路由。
第二,最大限度把引數正確性校驗提前,能在編譯時校驗就不要在執行時校驗。
第三,儘可能的支援多種註冊方式,靜態註冊、動態註冊、服務配置等。
下一篇我們將進行實操,跟大家一起一步步編寫一個模組間通訊的服務元件
文章首發GitHub:
架構和元件化系列文章預告:從0到1實現一個模組間通訊的服務元件,會一步步跟大家一起編寫一個模組間通訊的服務元件。
文章首發GitHub github.com/Lobster-Kin…