iOS 組件化方案


將一個工程分解成各個組件,然后按照某種方式任意組織成為一個擁有完整業務邏輯的工程。

大致討論組件化的三種方案:url-block、protocol-class(和 url-controller 類似)、target-action,以及應用這三種組件化方案的時機、步驟、利弊等。

一、為什么需要組件化

隨着公司業務的不斷發展,項目的功能越來越復雜,各個業務代碼耦合越來越多,代碼量急劇增加,傳統的 MVC 或者 MVVM 架構已經無法高效的管理工程代碼,因此需要用一種技術來更好地管理工程,而組件化是一種能夠解決代碼耦合的技術。項目經過組件化的拆分,不僅可以解決代碼耦合的問題,還可以增強代碼的復用性,工程的易管理性等。

二、什么時候做組件開發

  • 項目管理:項目的業務線超過 2 條以上,需要獨立拆分。隨着業務的拆分,對應的業務組件也就很自然的獨立出來。

  • 人員管理:過多人對同一塊代碼的進行修改,產生 bug 的可能性上升,所以需要重新分配人員和其維護的功能。

  • 測試維度:隨着項目的業務量增大,很難做單元測試。每個小功能修改,都需要對 App 進行測試,嚴重增加測試工作量。

當 App 業務之間交叉耦合,bug 率難以下降,測試每天做大量重復工作。開發人員之間修改相互影響時,你需要考慮進行組件化。

組件化是項目架構層面的技術,不是所有項目都適合組件化,組件化一般針對的是大中型的項目,並且是多人開發。如果,項目比較小,開發人員比較少,確實不太適合組件化,因為這時的組件化可能帶來的不是便捷,而是增加了開發的工作量。另外,組件化過程也要考慮團隊的情況,總之,根據目前項目的情況作出最合適的技術選型。

三、組件化的過程

3.1 url-block

這是蘑菇街中使用的一種頁面間調用的方式,通過在啟動時注冊組件提供的服務,把調用組件使用的 url 和組件提供的服務 block 對應起來,保存到內存中。在使用組件的服務時,通過 url 找到對應的 block,然后獲取服務。

url-block 的架構圖:

9

注冊:

[MGJRouter registerURLPattern:@"mgj://detail?id=:id" 
                    toHandler:^(NSDictionary * routerParameters) {
    NSNumber *id = routerParameters[@"id"];
    // create view controller with id. push view controller
}];

調用:

[MGJRouter openURL:@"mgj://detail?id=404"]

蘑菇街為了統一 iOS 和 Android 的平台差異性,專門用后台來管理 url,然后針對不同的平台生成不同類型的文件。

使用 url-block 的方案的確可以組件間解耦,但是還是存在其它明顯的問題,比如:

  1.  需要在內存中維護 url-block 的表,組件多了可能會有內存問題;
  2.  url 的參數傳遞受到限制,只能傳遞常規的字符串參數,無法傳遞非常規參數,如 UIImage、NSData 等類型;
  3.  沒有區分本地調用和遠程調用的情況,尤其是遠程調用會因為 url 參數受限,導致一些功能受限;
  4.  組件本身依賴了中間件,且分散注冊使的耦合較多

3.2 protocol-class

針對方案 1 的問題,蘑菇街又提出了另一種組件化的方案,就是通過 protocol 定義服務接口,組件通過實現該接口來提供接口定義的服務,具體實現就是把 protocol 和 class 做一個映射,同時在內存中保存一張映射表,使用的時就通過 protocol 找到對應的 class 來獲取需要的服務。

protocol-class 的架構圖:

10

注冊:

[ModuleManager registerClass:ClassA forProtocol:ProtocolA]

調用:

[ModuleManager classForProtocol:ProtocolA]

蘑菇街的這種方案確實解決了方案 1 中無法傳遞非常規參數的問題,使得組件間的調用更為方便,但是它依然沒有解決組件依賴中間件、內存中維護映射表、組件的分散調用的問題。設計思想和方案 1 類似,都是通過給組件加了一層 wrapper,然后給使用者調用。

3.3 url-controller

這是 LDBusMediator 的組件化方案。它是通過組件實現公共協議的接口,來對外提供服務。

具體就是通過單例來維護 url-controller 的映射關系表,根據調用者的 url,以及提供的參數(字典類型,所以參數類型不受約束)來返回對應的 controller 來提供服務;同時,為了增強組件提供服務的多樣性,又通過服務協議定義了其它的服務。整體來看,LDBusMediator 解決了蘑菇街的這兩種組件化方案的不足,比如:通過注冊封裝件 connector 而不是 block 來降低了內存占用;通過字典傳遞參數,解決了 url 參數的限制性。但是,由於使用了 connector 來提供服務而不是組件本身,把 connector 作為組件的一部分,依然有組件依賴中間件的問題。

LDBusMediator 的架構圖:

12

3.4 target-action

target-action 的方案是通過給組件包裝一層 wrapper 來給外界提供服務,然后調用者通過依賴中間件來使用服務;其中,中間件是通過 runtime 來調用組件的服務,是真正意義上的解耦,也是該方案最核心的地方。具體實施過程是給組件封裝一層 target 對象來對外提供服務,不會對原來組件造成入侵;然后,通過實現中間件的 category 來提供服務給調用者,這樣使用者只需要依賴中間件,而組件則不需要依賴中間件。

target-action 的架構圖:

7

- (UIViewController *)CTMediator_viewControllerForDetail
{
    return [self performTarget:kCTMediatorTargetA 
                        action:kCTMediatorActionNativFetchDetailViewController 
                        params:@{ @"key" : @"value" }
             shouldCacheTarget:NO];
}

但是 target-action 方案有個問題就是在中間件的 category 里有 hardcode,casa 的解釋是在組件間調用時,最好是去 model 化,所以不可避免的引入了 hardcode,並且所有的 hardcode 只存在於分類中。

針對這個問題,有人提議把所有的 model 做成組件化下沉,然后讓所有的組件都可以自由的訪問 model。這種方案雖然解決了組件間傳遞 model 的依賴問題,但是為了解決這個問題,直接把整個 model 層組件化后暴露給所有組件,容易造成數據泄露,付出的代價有點大。

針對這個問題,經過和網友討論,一致覺得組件間調用時用字典傳遞數據,組件內調用時用 model 傳遞數據,這樣既減少組件間數據對 model 的耦合,又方便了組件內使用 model 傳遞數據的便捷性。

- (UIViewController *)CTMediator_viewControllerForDetail:(NSDictionary *)dict
{
    return [self performTarget:kCTMediatorTargetA                         
                        action:kCTMediatorActionNativFetchDetailViewController
                        params:dict
             shouldCacheTarget:NO];
}

hardCode

  1. 官方解釋:將可變變量用一個固定值來代替的方法。用這種方法編譯后,如果以后需要更改此變量就非常困難了。

  2. hard code 是指“硬編碼”,即將數據直接寫在代碼中。也就是,在程序中直接給變量賦值。指的是在軟件實現上,把輸出或輸入的相關參數(例如:路徑、輸出的形式、格式)直接硬編碼在源代碼中,而非在運行時期由外界指定的設置、資源、數據、或者格式做出適當回應。

  3. hard code 的雙重性:

    a. 直接將數據填寫在源代碼中,數據發生變化時,並不利於數據的修改,會造成程序的質量降低;

    b. 保護一些數據,直接賦值,避免其發生變化。

四、組件化實施的方式

組件化可以利用 git 的源代碼管理工具的便利性來實施,具體就是建立一個項目工程的私有化倉庫,然后把各個組件的 podspec 上傳到私有倉庫,在需要用到組件時,直接從倉庫里面取。

  1. 殼工程

    • main
    • AppDelegate
    • 工程配置
    • Debug 頁面
  2. 封裝公共庫和基礎 UI 庫

    在具體的項目開發過程中,我們常會用到三方庫和自己封裝的 UI 庫,我們可以把這些庫封裝成組件,然后在項目里用 pod 進行管理。其中,針對三方庫,最好再封裝一層,使我們的項目部直接依賴三方庫,方便后續開發過程中的更換。

  3. 獨立業務模塊化

    在開發過程中,對一些獨立的模塊,如:登錄模塊、賬戶模塊等等,也可以封裝成組件,因為這些組件是項目強依賴的,調用的頻次比較多。另外,在拆分組件化的過程中,拆分的粒度要合適,盡量做到組件的獨立性。同時,組件化是一個漸進的過程,不可能把一個完整的工程一下子全部組件化,要分步進行,通過不停的迭代,來最終實現項目的組件化。

  4. 服務接口最小化

    在前兩步都完成的情況下,我們可以根據組件被調用的需求來抽象出組件對外的最小化接口。這時,就可以選擇具體應用哪種組件化方案來實施組件化了。

    公共組件:

    • 埋點組件
    • Common 組件(聚合工具類)
    • 啟動組件
    • 性能監控組件
    • 定位組件
    • 圖片處理組件
    • UIKit 封裝和擴展組件
    • 業務生命周期及通信組件

    網絡組件:

    • 基於 AFNetworking 進行封裝,提供 JSON 轉 Model、緩存功能
    • DNS 加速組件

    持久化組件

    • 基於 FMDB 進行封裝組件

    第三方業務組件

    • 分享組件
    • 推送組件

    基礎業務組件

    • User 組件,保存用戶信息,登陸,登出狀態

五、文章

iOS 組件化方案的幾種實現
iOS 的組件化開發


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM