歡迎光臨
每天分享高質量文章

iOS 從零到一搭建組件化專案框架

作者:光強_上海

鏈接:https://www.jianshu.com/p/59c2d2c4b737


隨著公司業務需求的不斷迭代發展,工程的代碼量和業務邏輯也越來越多,原始的開發樣式和架構已經無法滿足我們的業務發展速度了,這時我們就需要將原始專案進行一次重構大手術了。這時我們應該很清晰這次手術的動刀口在哪,就是之前的高度耦合的業務組件和功能組件,手術的目的就是將這些耦合拆分成互相獨立的各個組件。

組件化工程示例專案地址

組件化開源專案Git倉庫地址

https://github.com/guangqiang-liu/iOS-Component-Pro

下麵我們圍繞這幾個問題來展開講解

  • 為什麼要用組件化,它給我們帶來哪些優勢

  • 各個組件該如何進行拆分,拆分的顆粒度該如何控制

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

為什麼要用組件化

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

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

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

從上面我們列舉的一個簡單的例子可以體會到:在專案沒有做到真真意義上的組件化之前,各個業務模塊和業務模塊間的高度耦合,功能組件和功能組件間的高度耦合對未來公司的業務擴展來說,成本很高,不能做到同樣業務邏輯的代碼的高度復用,這樣對我們開發來說也是效率的降低。

好了,有的同學可能會說,既然上面各個模塊間耦合這麼高,那我就來將這些耦合解耦,於是,可能會出現下麵這張圖的模塊間的關係。

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

從上面的這張圖,我們可以看出,各個業務模塊間只會依賴中間調度者,並且中間調度者不對各個模塊產生任何的依賴。

好了,從上面的三張圖之間的對比,我們就可以很好的理解為什麼我們的工程急需要實現組件化架構開發了,以及各自的優劣勢。

各個組件該如何進行拆分

關於組件該如何拆分,這個沒有一個完整的標準,因為每個公司的業務場景不一樣,對應衍生出來的各個業務模塊也就不一樣,所以業務組件間的拆分,這個根據自己公司的業務模塊來進行合理的劃分即可。這裡我們來說下整個工程的組件大致的劃分方向


1、專案主工程:當我們工程完全使用組件化架構進行開發後,我們會驚奇的發現我們的主工程就成了一個空殼子工程。因為所有的主工程呈現出來的內容都被拆分成了各個獨立的業務組件了,包括各個工具組件也是各自互相獨立的。這樣我們發現開發一個完整的APP就像是搭建樂高積木一樣,各個部件都有,任我們隨意的組合搭建,這樣是不是感覺很爽。


2、業務組件:業務組件就是我們上面示例圖所示的各個獨立的產品業務功能模塊,我們將其封裝成獨立的組件。例如示例Demo中的電子發票業務組件,業務組件A,業務組件B。我們通過組裝各個獨立的業務組件來搭建一個完整的APP專案。


3、基礎工具類組件:基礎工具類是各個互相獨立,沒有任何依賴的工具組件。它們和其它的工具組件、業務組件等沒有任何依賴關係。這類組件例如有:對陣列,字典進行異常保護的Safe組件,對陣列功能進行擴展Array組件,對字串進行加密處理的加密組件等等。


4、中間件組件:這個組件比較特殊,這個是我們為了實現組件化開發而衍生出來的一個組件,上面示例圖中的中間調度者就是一個功能獨立的中間件組件。


5、基礎UI組件:視圖組件就比較常見了,例如我們封裝的導航欄組件,Modal彈框組件,PickerView組件等。


6、業務工具組件:這類組件是為各個業務組件提供基礎功能的組件。這類組件可能會依賴到其他的組件。例如:網絡請求組件,圖片快取組件,jspatch組件等等

至於組件的拆分顆粒度,這個著實不好去斷定,因人而異,不同的需求功能複雜度拆分出來的組件大小也不盡相同

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

在講如何從零到一來實現一個組件化架構專案前,我們需要熟練掌握使用pod來製作組件庫。下麵我們就圍繞提供的組件化示例專案來展開講解。

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

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

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

註意了:這裡提供的6個工具組件也都是作者已經封裝好的功能組件,大家也可以直接 install 安裝使用的哦。

詳細操作步驟

第一步:

我們先創建一個空的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 

- (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 吧,這個是中間件核心函式。

核心函式代碼塊如下:

記得上面第八步中,我們呼叫這個函式傳遞的引數吧,我們在把呼叫代碼拿過來看下

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

我們可以看到 TargetName 是我們傳遞的 ModuleB,action是我們傳遞的viewController,然後我們將 這兩個引數傳給了下麵的函式:

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

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

這個函式最終呼叫到蘋果官方提供的函式:[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(00300100);
    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
#import

@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 實體。

細心的小伙伴們發現,咦!我們在第九步中打印出來的target 和 action 不就正是Target檔案的Target_ModuleB 和 Action_viewController: 。

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

好了,現在小伙伴們應該看懂了這一連串的工作流程了吧,如果還沒有看懂,可以看看Casa的講解CTMediator。作者建議直接運行提供的示例Demo專案進行除錯,這樣便於理解各個組件之間的關係。

組件化工程示例專案地址

組件化開源專案Git倉庫地址

https://github.com/guangqiang-liu/iOS-Component-Pro

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

總結

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

好了,又寫到凌晨了,不早了,本篇教程到此就講完啦。下篇教程講解如何使用MVVM+RAC進行實戰開發。小伙伴們,感覺文章對你有幫忙,簡書點個贊唄,開源組件化工程專案 RAC+MVVM 也幫忙點個 star ,先謝過啊。

參考文獻

本篇文章主要借鑒了casatwy的CTMediator思想重新實踐了一遍,下麵也有蘑菇街的MGJRouter 和 阿裡的 BeeHive 供大家學習參考。

  • https://casatwy.com/modulization_in_action.html

  • https://github.com/casatwy/CTMediator

  • https://github.com/alibaba/BeeHive/blob/master/README-CN.md

  • http://limboy.me/tech/2016/03/10/mgj-components.html

  • https://github.com/meili/MGJRouter


編號319,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

赞(0)

分享創造快樂