在看了不少其餘的方案以後,首先對組件化思想上有一個小分歧。我認爲不少人對於 iOS 中組件化的理解實際上是有誤區的。作 Flex 開發,其中就有不少組件化的思想,加上最近在用 Vue 作 web 項目以後,更爲意識到你們在 iOS 開發上說的組件化有點不合適。 首先我認爲組件是一個相對比較小的功能模塊,不須要與外界有太多的通訊,更不能依賴其餘第三方,這一點尤其重要。好比說幾乎全部 iOS 開發者知道的 MJRefresh,幾乎不依賴業務,而且提供了良好的調用接口和使用體驗的才能稱爲組件。而看了不少方案,大部分都是在講 App 裏面的業務能組件之間的通訊和解耦,其實我更願意將這些東西稱爲 「模塊」。那如何區分這兩種呢,我以爲這句話比較好理解:核心業務模塊化,通用功能組件化。 打比方說你的 App 是一個電商項目,name 你的產品詳情頁、列表頁、購物車、搜索等頁面確定就是調用頻次很是高的 VC 了,這些界面之間跳轉都會很是頻繁。這就形成了互相依賴而且高度耦合。以下圖所示: 前端
客戶端在公司業務發展的過程當中體積愈來愈龐大,其中堆疊了大量的業務邏輯代碼,不一樣業務模塊的代碼相互調用,相互嵌套,代碼之間的耦合性愈來愈高,調用邏輯會愈來愈混亂。當某個模塊須要升級的時候,改動代碼的時候每每會有牽一髮而動全身的感受。特別是若是工程量出設計的時候沒有考慮接口的封裝,而將大量的業務代碼與功能模塊代碼混在一塊兒時,未來的升級就須要對代碼進行大量修改及調整,帶來的功工做量是很是巨大的。這就須要設計一套符合要求的組件之間通訊的中間件。模塊化能夠將代碼的功能邏輯儘可能封裝在一塊兒,對外只提供接口,業務邏輯代碼與功能模塊經過接口進行弱耦合。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];
複製代碼
可是這樣寫的話也有一個問題,每一個 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;
}
複製代碼
這樣上面的圖就是這樣的: 組件化
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 中均可能很好的應用,而且解決大部分的業務需求。