iOS 從零到一搭建組件化項目框架

隨着公司業務需求的不斷迭代發展,工程的代碼量和業務邏輯也愈來愈多,原始的開發模式和架構已經沒法知足咱們的業務發展速度了,這時咱們就須要將原始項目進行一次重構大手術了。這時咱們應該很清晰此次手術的動刀口在哪,就是以前的高度耦合的業務組件和功能組件,手術的目的就是將這些耦合拆分紅互相獨立的各個組件。html

工程效果預覽

image

組件化工程示例項目地址

組件化開源項目Git倉庫地址vue

下面咱們圍繞這幾個問題來展開講解

  • 爲何要用組件化,它給咱們帶來哪些優點
  • 各個組件該如何進行拆分,拆分的顆粒度該如何控制
  • 如何從零到一搭建組件化架構項目

爲何要用組件化

咱們先來張圖看看在沒有使用組件化前,咱們各個模塊間的依賴關係ios

image

從上面這種各個業務組件的依賴關係來看,他們是互相依賴的,業務組件和業務組件間產生了嚴重的耦合關係,這樣一來對咱們工程的擴展性就會大大的下降,維護成本就會變高。git

舉個例子:假設某天產品經理說,我們公司的業務發展的太好了,我們的營銷模塊須要獨立出來成一個單獨的應用,以便於我們能夠添加更多高效的營銷手段。這時咱們就傻眼了,須要獨立出一個app出來,這可怎麼搞啊,營銷模塊的代碼和其餘的不少業務代碼耦合在一塊兒了,如今要獨立出來,那就只能從新寫一個營銷應用了,以前的代碼剝離不乾淨了。github

從上面咱們列舉的一個簡單的例子能夠體會到:在項目沒有作到真真意義上的組件化以前,各個業務模塊和業務模塊間的高度耦合,功能組件和功能組件間的高度耦合對將來公司的業務擴展來講,成本很高,不能作到一樣業務邏輯的代碼的高度複用,這樣對咱們開發來講也是效率的下降。小程序

好了,有的同窗可能會說,既然上面各個模塊間耦合這麼高,那我就來將這些耦合解耦,因而,可能會出現下面這張圖的模塊間的關係。設計模式

image

從下面這張圖來看,咱們發現,如今確實能作到各個業務模塊間徹底的解耦了,他們再也不互相依賴了,同時咱們引入了一箇中間調度者的一個角色,如今是各個業務模塊和這個中間調度者角色產出了嚴重的依賴。咱們思考下發現,咱們的各個業務模塊依賴這個中間調度者,這個是徹底正常的,由於他們須要這個調度者來作統一的事件分發工做,可是這個調度者卻又依賴了每一個業務模塊,這層依賴是有必要的嗎?咱們回頭想一想真正的組件化開發是徹底的去依賴化,這個依賴是徹底沒有必要的。例如:假設咱們如今有一個新的B APP須要開發,這時咱們也須要用到這個中間調度者組件,可是咱們不能直接拿過來用,由於它又依賴了不少A App的業務組件。所以,咱們的組件化架構設計又須要一次升級變動了,升級成以下圖所示的模型。數組

image

從上面的這張圖,咱們能夠看出,各個業務模塊間只會依賴中間調度者,而且中間調度者不對各個模塊產生任何的依賴。緩存

好了,從上面的三張圖之間的對比,咱們就能夠很好的理解爲何咱們的工程急須要實現組件化架構開發了,以及各自的優劣勢。bash

各個組件該如何進行拆分

關於組件該如何拆分,這個沒有一個完整的標準,由於每一個公司的業務場景不同,對應衍生出來的各個業務模塊也就不同,因此業務組件間的拆分,這個根據本身公司的業務模塊來進行合理的劃分便可。這裏咱們來講下整個工程的組件大體的劃分方向

  1. 項目主工程:當咱們工程徹底使用組件化架構進行開發後,咱們會驚奇的發現咱們的主工程就成了一個空殼子工程。由於全部的主工程呈現出來的內容都被拆分紅了各個獨立的業務組件了,包括各個工具組件也是各自互相獨立的。這樣咱們發現開發一個完整的APP就像是搭建樂高積木同樣,各個部件都有,任咱們隨意的組合搭建,這樣是否是感受很爽。
  2. 業務組件:業務組件就是咱們上面示例圖所示的各個獨立的產品業務功能模塊,咱們將其封裝成獨立的組件。例如示例Demo中的電子發票業務組件,業務組件A,業務組件B。咱們經過組裝各個獨立的業務組件來搭建一個完整的APP項目。
  3. 基礎工具類組件:基礎工具類是各個互相獨立,沒有任何依賴的工具組件。它們和其它的工具組件、業務組件等沒有任何依賴關係。這類組件例若有:對數組,字典進行異常保護的Safe組件,對數組功能進行擴展Array組件,對字符串進行加密處理的加密組件等等。
  4. 中間件組件:這個組件比較特殊,這個是咱們爲了實現組件化開發而衍生出來的一個組件,上面示例圖中的中間調度者就是一個功能獨立的中間件組件。
  5. 基礎UI組件:視圖組件就比較常見了,例如咱們封裝的導航欄組件,Modal彈框組件,PickerView組件等。
  6. 業務工具組件:這類組件是爲各個業務組件提供基礎功能的組件。這類組件可能會依賴到其餘的組件。例如:網絡請求組件,圖片緩存組件,jspatch組件等等

至於組件的拆分顆粒度,這個着實很差去判定,因人而異,不一樣的需求功能複雜度拆分出來的組件大小也不盡相同

如何從零到一搭建組件化架構

在講如何從零到一來實現一個組件化架構項目前,咱們須要熟練掌握使用pod來製做組件庫。下面咱們就圍繞提供的組件化示例項目來展開講解。

首先,咱們來看示例Demo中包含哪些業務組件(以下圖所示:):

image

示例Demo中,我提供了三個業務組件來做爲演示效果,其中業務模塊A和業務模塊B是臨時業務模塊組件,電子發票業務組件時真實的企業需求功能組件。

咱們再來看下示例Demo中都提供了哪些工具組件(以下圖所示)

注意了:這裏提供的6個工具組件也都是做者已經封裝好的功能組件,你們也能夠直接 install 安裝使用的哦。

image

詳細操做步驟

第一步:

咱們先建立一個空的iOS工程項目:MainProject,這個空項目做爲咱們的主工程項目,就是上面所說的殼子工程項目,而後初始化pod,這裏不清楚pod的使用的小夥伴們請自行查閱資料。

第二步:

咱們建立一個空工程項目:ModuleA,這個ModuleA 項目做爲咱們的業務A組件。而後咱們初始化pod,初始化podspec文件。

第三步:

咱們建立一個空工程項目:ModuleB,這個ModuleB 項目做爲咱們的業務B組件。而後咱們初始化pod,初始化podspec文件。

第四步:

咱們建立一個空工程項目:ComponentMiddleware,這個項目就是咱們上面所說的中間調度者。而後咱們初始化pod,初始化podspec文件。

第五步:

咱們建立一個空工程項目: ModuleACategory,這個工程是對應業務組件A的一個分類工程。而後咱們初始化pod,初始化podspec文件。

第六步:

咱們建立一個空工程項目: ModuleBCategory,這個工程是對應業務組件B的一個分類工程。而後咱們初始化pod,初始化podspec文件。

好了,上面的主工程和兩個業務組件工程,以及兩個組件分類工程都已建立完畢,下面咱們來說解他們各個之間如何工做的。我就從主工程加載業務組件開始往下捋,順藤摸瓜式的引出每一個工程的用意。

第七步:

咱們在主工程MainProject的Podfile中引入咱們的業務組件B工程ModuleB,以及引入咱們的ModuleB的分類工程:ModuleBCategory。而後咱們pod install。這時已將這兩個組件庫引入到咱們的主工程中了。

示例代碼以下:

# Uncomment the next line to define a global platform for your project
platform :ios, '8.0'

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/guangqiang-liu/GQSpec.git'

target 'GQComponentDemo' do
  
  pod 'ModuleB'
  pod 'ModuleBCategory'
end
複製代碼

而後咱們在主工程中添加一個按鈕事件,這個事件是點擊 push 到業務組件B的 頁面。

示例代碼以下:

#import <ModuleBCategory/ComponentScheduler+ModuleB.h>

- (void)moduleB {
    UIViewController *VC = [[ComponentScheduler sharedInstance] ModuleB_viewControllerWithCallback:^(NSString *result) {
        NSLog(@"resultB: --- %@", result);
    }];
    [self.navigationController pushViewController:VC animated:YES];
}
複製代碼

第八步:

上面第七步中,咱們用到了ModuleBCategory 這個分類工程。這個工程咱們只對外暴露了兩個文件。這兩文件是上面的中間調度者的分類,也就是說是中間件的分類。咱們先來看下這個分類文件的.h 和.m 實現。

.h

#import "ComponentScheduler.h"

@interface ComponentScheduler (ModuleB)

- (UIViewController *)ModuleB_viewControllerWithCallback:(void(^)(NSString *result))callback;

@end
複製代碼

.m

#import "ComponentScheduler+ModuleB.h"

@implementation ComponentScheduler (ModuleB)

- (UIViewController *)ModuleB_viewControllerWithCallback:(void(^)(NSString *result))callback {
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    params[@"callback"] = callback;
    return [self performTarget:@"ModuleB" action:@"viewController" params:params shouldCacheTarget:NO];
}
@end

複製代碼

咱們發現這個分類實現很是的簡單,就是對外暴露一個函數,而後執行[self performTarget:@"ModuleB" action:@"viewController" params:params shouldCacheTarget:NO]; ,並將執行的返回值返回出去。

這個分類的做用你能夠理解爲咱們提早約定好Target的名字和Action的名字,由於這兩個名字中間件組件中會用到。

上面的performTarget:action:params:shouldCacheTarget 函數是中間件提供的函數。由於ModuleBCategory 是 ComponentScheduler(中間件)的分類文件,因此能夠調用到這個函數啦。

在ModuleBCategory 工程中須要引用到了中間件工程因此咱們須要在ModuleBCategory 的Podfile文件中引用 中間件組件

示例代碼以下:

# Uncomment the next line to define a global platform for your project
platform :ios, '8.0'

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/guangqiang-liu/GQSpec.git'

target 'ModuleB-Category' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!

  # Pods for ModuleB-Category
  
  pod 'ComponentScheduler'

end
複製代碼

第九步:

由於上面第八步中引用到中間件工程,這裏咱們就來看下中間件工程到底作了什麼工做。還記得上面第八步中,咱們調用了一箇中間件提供的函數:performTarget:action:params:shouldCacheTarget 吧,這個是中間件核心函數。

核心函數代碼塊以下:

image

還記得上面第八步中,咱們調用這個函數傳遞的參數吧,咱們在把調用代碼拿過來看下

[self performTarget:@"ModuleB" action:@"viewController" params:params shouldCacheTarget:NO];

咱們能夠看到 TargetName 是咱們傳遞的 ModuleBaction是咱們傳遞的viewController,而後咱們將 這兩個參數傳給了下面的函數:

[self safePerformAction:action target:target params:params];

咱們來看下這兩個參數的值具體是什麼:

image

這個函數最終調用到蘋果官方提供的函數:[target performSelector:action withObject:params];

看到 performSelector: withObject: 你們應該就比較熟悉了,iOS的消息傳遞機制。

[Target_ModuleB performSelector:Action_viewController withObject:params];
複製代碼

上面這行僞代碼意思是: Target_ModuleB這個類 調用它的 Action_viewController: 方法,而後傳遞的參數爲 params

細心的小夥伴們就會發現,咱們沒有看到過哪裏有這個Target_ModuleB 類啊,更沒有看到Target_ModuleB 調用它的 Action_viewController: 方法啊。

是的,這個Target_ModuleB類和類的Action_viewController方法就在第十步中講解到。

第十步:

終於到了最後一步了,寫的好艱辛,嗯,小夥們不要捉急,快了,快講完了

細心的小夥們發現,咱們上面講的9步中,好像都沒有提業務組件B的東西。是的,業務組件B除了提供組件B的業務功能外,業務組件B還須要爲咱們提供一個Target文件。

咱們先來看下業務組件B的業務代碼:

示例代碼以下:

#import "ModuleBViewController.h"
#import "PageBViewController.h"

@interface ModuleBViewController ()

@end

@implementation ModuleBViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.title = @"我是模塊B業務組件";
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    btn.frame = CGRectMake(0, 0, 300, 100);
    btn.backgroundColor = [UIColor greenColor];
    btn.center = self.view.center;
    [btn setTitle:@"模塊B業務功能組件" forState: UIControlStateNormal];
    [btn addTarget:self action:@selector(push) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}

- (void)push {
    PageBViewController *VC = [[PageBViewController alloc] init];
    [self.navigationController pushViewController:VC animated:YES];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
複製代碼

咱們發現,業務組件B的業務代碼也很簡單,就是作一個push 跳轉操做,從PageA 控制器跳轉到 PageB 控制器。 這個沒有什麼好講的

咱們再來看上面提到的target文件

示例代碼以下:

.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface Target_ModuleB : NSObject

- (UIViewController *)Action_viewController:(NSDictionary *)params;

@end
複製代碼

.m

#import "Target_ModuleB.h"
#import "ModuleBViewController.h"

@implementation Target_ModuleB

- (UIViewController *)Action_viewController:(NSDictionary *)params {
    ModuleBViewController *VC = [[ModuleBViewController alloc] init];
    return VC;
}

@end

複製代碼

從上面的實現文件中,咱們能夠看到,Target文件的做用也很簡單,就是爲咱們提供導航跳轉的目標控制器實例對象。這裏的目標控制器實例就是業務組件B的ModuleBViewController 實例。

細心的小夥伴們發現,咦!咱們在第九步中打印出來的targetaction 不就正是Target文件的Target_ModuleBAction_viewController:

上面咱們只是串講了業務組件B的一系列流程,業務組件A的用法和業務組件B的用法同樣,若是後面再有業務組件C,D,都是同樣的道理,就再也不一一講解了。

好了,如今小夥伴們應該看懂了這一連串的工做流程了吧,若是尚未看懂,能夠看看Casa的講解CTMediator。做者建議直接運行提供的示例Demo項目進行調試,這樣便於理解各個組件之間的關係。

組件化工程示例項目地址

組件化開源項目Git倉庫地址

最後,咱們再來看張組件化完整的架構圖:

image

總結

上面咱們講解的只是簡單的項目組件化架構的基礎框架搭建,可是在真正的企業開發中,咱們只搭建這樣一個簡單項目框架結構還遠遠不能知足需求的開發,咱們還須要在項目框架中添枝加葉來知足現有需求。在上面提供的示例Demo中,我將電子發票業務組件獨立成一個完整的工程,並結合了當下比較流行的MVVM設計模式和RAC數據綁定框架來實現電子發票模塊的功能開發。若是有小夥們對 MVVM + RAC 實戰開發感興趣的,能夠單獨 install 電子發票工程查看,工程地址:iOS-MVVM-RAC

好啦,又一次寫到凌晨了,不早了,本篇教程到此就講完了。下篇教程講解如何使用MVVM+RAC進行實戰開發。小夥伴們,感受文章對你有幫忙,幫忙點個讚唄,開源組件化工程項目 iOS-Component-Pro 也幫忙點個 star 吧 ,先謝過了。

參考文獻

本篇文章主要借鑑了casatwy的CTMediator思想從新實踐了一遍,下面也有蘑菇街的MGJRouter 和 阿里的 BeeHive 供你們學習參考。

更多文章

  • 做者開源React Native項目OneM(按照企業開發標準搭建框架):OneM:歡迎小夥伴們 star
  • 做者開源mpvue美團外賣小程序:mpvue-meituan:歡迎小夥伴們 star
  • 做者掘金技術專欄:juejin.im/user/590ebe…
  • 做者簡書主頁:包含60多篇RN開發相關的技術文章www.jianshu.com/u/023338566… 歡迎小夥伴們:多多關注多多點贊
  • React Native QQ技術交流羣(600+ RN工程師):620792950 歡迎小夥伴進羣交流學習
  • iOS QQ技術交流羣:678441305 歡迎小夥伴進羣交流學習

歡迎小夥伴掃碼進iOS技術交流羣

相關文章
相關標籤/搜索