爲何要使用組件化???

什麼是組件化?

  在看了不少其餘的方案以後,首先對組件化思想上有一個小分歧。我認爲不少人對於 iOS 中組件化的理解實際上是有誤區的。作 Flex 開發,其中就有不少組件化的思想,加上最近在用 Vue 作 web 項目以後,更爲意識到你們在 iOS 開發上說的組件化有點不合適。   首先我認爲組件是一個相對比較小的功能模塊,不須要與外界有太多的通訊,更不能依賴其餘第三方,這一點尤其重要。好比說幾乎全部 iOS 開發者知道的 MJRefresh,幾乎不依賴業務,而且提供了良好的調用接口和使用體驗的才能稱爲組件。而看了不少方案,大部分都是在講 App 裏面的業務能組件之間的通訊和解耦,其實我更願意將這些東西稱爲 「模塊」。那如何區分這兩種呢,我以爲這句話比較好理解:核心業務模塊化,通用功能組件化。   打比方說你的 App 是一個電商項目,name 你的產品詳情頁、列表頁、購物車、搜索等頁面確定就是調用頻次很是高的 VC 了,這些界面之間跳轉都會很是頻繁。這就形成了互相依賴而且高度耦合。以下圖所示: 前端

在這裏插入圖片描述
  像商品詳情頁這些一般外部調入只須要傳入一個 productID 就能夠,並且高度依賴本身的業務功能的模塊就能夠將這些當成一個模塊維護。後面須要修改裏面的活動的顯示、業務的增刪均可以單獨在詳情模塊裏面改動而不須要改動別的代碼。   而對於組件,比方說我上面提到的 IM 類型的 App 中用到的聊天鍵盤,或者集成支付寶、微信等支付功能的支付插件。這些能夠在多個不一樣的項目小組內部共享。甚至能夠開源到社區中提供全部開發者使用的小插件,用組件來形容更貼切。在 Flex、Vue、angular 等前端開發中提現尤其突出。

爲何要有組件化(模塊化)

  客戶端在公司業務發展的過程當中體積愈來愈龐大,其中堆疊了大量的業務邏輯代碼,不一樣業務模塊的代碼相互調用,相互嵌套,代碼之間的耦合性愈來愈高,調用邏輯會愈來愈混亂。當某個模塊須要升級的時候,改動代碼的時候每每會有牽一髮而動全身的感受。特別是若是工程量出設計的時候沒有考慮接口的封裝,而將大量的業務代碼與功能模塊代碼混在一塊兒時,未來的升級就須要對代碼進行大量修改及調整,帶來的功工做量是很是巨大的。這就須要設計一套符合要求的組件之間通訊的中間件。模塊化能夠將代碼的功能邏輯儘可能封裝在一塊兒,對外只提供接口,業務邏輯代碼與功能模塊經過接口進行弱耦合。web

個人模塊化架構思路

如何優化模塊化之間的通訊

  封裝模塊的工做只要你對面向對象思想有所理解,實現起來應該不難,確保寫好調用接口就行,這裏再也不贅述。而模塊化最重要的就是各個模塊之間的通訊。好比在商品搜索列表頁面,須要查看購物車功能和查看商品詳情功能,購物車的商品列表也能點擊商品到商品詳情頁。等等這些頁面之間都會相互調用,相互依賴。一般咱們會怎麼實現呢?好比這樣:bash

#import "ProductDetailViewController.h"
#import "CartViewController.h"

@implementation ProductListViewController

- (void)gotoDetail {
	ProductDetailViewController *detailVC = [[ProductDetailViewController alloc] initWithProId:self.proId];
	[self.navigationController pushViewController:detailVC animated:YES];
}

- (void)gotoCart {
	CartViewController *cartVC = [[CartViewController alloc] init];
	[self.navigationController pushViewController:cartVC animated:YES];
}

@end
複製代碼

  相信這樣的代碼你們都不陌生,基本都是這樣作。並且這樣也並無問題。可是,項目一旦大起來問題就來了。各個模塊只要有相互調用的狀況,都會相互產生依賴。每次跳轉都須要 import 對應的控制器,重寫一次代碼。若是某個地方作了一點點需求改動,好比商品詳情頁須要多傳入一個參數,這個時候就要找到各個調用的地方逐一修改,這顯然不是高效的辦法。微信

運用中間件

  因而很簡單的就想到了一個方法,提供一箇中間層:Router。在 router 裏面定義好每次跳轉的方法,而後再須要用的界面調用 router 函數,傳入對應的參數。好比這樣: 架構

在這裏插入圖片描述

// Router.m
#import "ProductDetailViewController.h"
#import "CartViewController.h"

@implementation Router

+ (UIViewController *)getDetailWithParam:(NSString *)param {
	ProductDetailViewController *detailVC = [[ProductDetailViewController alloc] initWithProId:self.proId];
	return detailVC;
}

+ (UIVIewController *)getCart {
	CartViewController *cartVC = [[CartViewController alloc]init];
	return cartVC;
}

@end
複製代碼

  其餘界面中這樣使用:模塊化

#import "Router.m"

UIViewController *detailVC = [[Router instance] jumpToDetailWithParam:param];
[self.navigationController pushViewController:detailVC];
複製代碼
運行 runtime

  可是這樣寫的話也有一個問題,每一個 vc 都會依賴 Router,而 Router 裏面會依賴全部的 VC。name如何打破這層循環引用呢?OC 裏有個法寶能夠用到:runtime。函數

- (UIViewController *)getViewController:(NSString *)stringVCName {
	Class class = NSClassFromString(stringVCName);
	UIViewController *controller = [[class alloc]init];
	if (controller == nil) {
		NSLog("未找到此類:%@",stringVCName);
		controller = [[RouterError sharedInstance] getErrorController];
	}
	return controller;
}
複製代碼

  這樣上面的圖就是這樣的: 組件化

在這裏插入圖片描述
  這樣 Router 裏面不須要 import 任何 VC 了,代碼也就數十行而已,看起來很是的簡便。並且作了異常處理,若是找不到此類,會返回預先設置的錯誤界面。是否是有點相似於 web 開發中的 404 界面呢?

UIViewController *controller = [[Router shaedInstance] getViewController:@"ProductDetailViewController"];
[self.navigationController pushViewController:controller];
複製代碼
如何傳參數

  不少人確定都發現了,這樣寫的話如何傳遞參數呢。好比商品詳情頁至少要傳一個 productID 吧。別急,咱們能夠將上面的方法稍微處理,傳入一個 dict 作了參數:優化

- (UIViewController *)getViewController:(NSString *)stringVCName  {
	Class class = NSClassFromString(stringVCName);
	UIViewController *controller = [[class alloc]init];
	return controller;
}

- (UIViewController *)getViewController:(NSString *)stringVCName witParam:(NSDictionary *)paramdic {
	UIViewController *controller = [self getViewController:stringVCName];
	if (controller != nil) {
		controller = [self controller:controller withParam:paramdict andVCname:stringVCName];
	} else {
		NSLog(@"未找到此類:%@",stringVCName);
		// EXCEPTION Push 啊 Normal Error VC
		controller = [[RouterError sharedInstance] getErrorController];
	}
	return controller;
}

/**
	此方法用來初始化參數(控制器初始化方法默認爲 initViewControllerParam。初始化方法能夠自定義,前提是 VC 必須實現它。要想靈活一點,也能夠添加一個參數 actionName,當作參數傳入。不過這樣你就須要修改此方法了)。
	@param controller 獲取到的實例 VC
	@param paramdic 實例化參數
	@param vcName 控制器名字
	@return 初始化以後的VC
*/
- (UIViewController *)controller:(UIViewController *)controller withParam:(NSDictionary *)paradic andVCname:(NSString *)vcName {
	SEL selector = NSSelectorFromString(@"initViewControllerParam:");
	if(![controller respondsToSelector:selector]) { // 若是沒定義初始化參數方法,直接返回,不必在往下作設置參數的方法
		NSLog(@"目標類:%@ 未定義:%@方法",controller,@"initViewControllerParam:");
		return controller;
	}
	// 在初始化參數裏面添加 key 信息,方便控制器中檢驗路由信息
	if (paradic == nil) {
		paramdic = [[NSMutableDictionary alloc] init];
		[paradic setValue:vcName forKey:@"URLKEY"];
		SuppressPerformSlelctorLeakWarning([controller performSelector:selector withObject:paramdic]);
	} else {
		[paramdic setValue:vcName forKey:@"URLKEY"];
	}
	SuppressPerformSelecorLeakWarning([controller performSelector:selector withObject:paramdic]);
	return controller;
}
複製代碼

  咱們默認在業務控制器裏面有個 initViewControllerParam 方法,而後再 router 裏面能夠用 respondsToSelector 手動觸發這個方法,傳入參數 paramdic。固然若是你想要更加靈活一點,那就將 initViewControllerParam 初始化方法當作一個 actionName 參數傳到 router 裏面。相似於這樣:ui

- (UIViewController *)controller:(UIViewController *)controller withParam:(NSDictionary *)paramdic andVCName:(NSString *)vcName actionName:(NSString *)actionName {
	SEL selector = NSSelectorFromString(actionName);
	... 後面就是同樣的代碼了
}
複製代碼

  到這裏基本上模塊化就是能夠實現了。基本上經過超過 100 行的代碼解決各個複雜業務模塊之間的通訊和高度解耦。

總結

  模塊化的實現方法在 iOS 開發彙總算是比較好實現的,主要是 OC 自己就是一門動態的語言。對象類型是加上是在運行時中肯定的,而調用方法在 OC 中是以發消息的形式實現。這就增長了不少能夠操做的可能性。這種方法在大部分的 App 中均可能很好的應用,而且解決大部分的業務需求。

相關文章
相關標籤/搜索