iOS組件化
- 組件化的原因
- 現在流行的組件化方案
- 方案一、url-block (基于 URL Router)
- 方案二、protocol
- 調用方式解讀
- 方案三、target-action
- 調用方式解讀
- gitHub代碼鏈接參考
組件化的原因
- 模塊間解耦
- 模塊重用
- 提高團隊協作開發效率
- 單元測試
當項目App處于起步階段、各個需求模塊趨于成熟穩定的過程中,組件化也許并沒有那么迫切,甚至考慮組件化的架構可能會影響開發效率和需求迭代。而當項目迭代到一定時期之后,便會出現一些相對獨立的業務功能模塊,而團隊的規模也會隨著項目迭代逐漸增長,這便是中小型應用考慮組件化的時機了。
為了更好的分工協作,團隊會安排團隊成員各自維護一個相對獨立的業務組件。這個時候我們引入組件化方案,一是為了解除組件之間相互引用的代碼硬依賴,二是為了規范組件之間的通信接口; 讓各個組件對外都提供一個黑盒服務,而組件工程本身可以獨立開發測試,減少溝通和維護成本,提高效率。
進一步發展,當團隊涉及到轉型或者有了新的立項之后,一個團隊會開始維護多個項目App,而多個項目App的需求模塊往往存在一定的交叉,而這個時候組件化給我們的幫助會更大,我只需要將之前的多個業務組件模塊在新的主App中進行組裝即可快速迭代出下一個全新App。
現在流行的組件化方案
方案一、url-block (基于 URL Router)
通過在啟動時(load方法中)注冊組件提供的服務,把調用組件使用的url和組件提供的服務block對應起來,保存到內存中。在使用組件的服務時,通過url找到對應的block,然后通過block回調獲取服務。
url-block的架構圖如下:
代碼說明
首先要在 + (void)load
中進行注冊
[LYXRouter registerURLPattern:@"lyx://foo/bar" toHandler:^(NSDictionary *routerParameters) {// create view controller// push view controllerstringWithFormat:@"routerParameters:%@", routerParameters]];}];
然后調用的時候傳入url:
[MGJRouter openURL:@"lyx://foo/bar"];
代碼內部會通過傳入的url,拿到對應的block,然后進行調用。
之后block中的回調內部 就會執行 跳轉頁面的代碼了。
使用url-block的方案的確可以組建間的解耦,但是還是存在其它明顯的問題,比如:
- 需要在內存中維護url-block的表,組件多了可能會有內存問題
- url的參數傳遞受到限制,只能傳遞常規的字符串參數,無法傳遞非常規參數,如UIImage、NSData等類型
- 沒有區分本地調用和遠程調用的情況,尤其是遠程調用,會因為url參數受限,導致一些功能受限
- 組件本身依賴了中間件,且分散注冊使的耦合較多
方案二、protocol
是通過protocol
定義服務接口,組件通過實現該接口來提供接口定義的服務,具體實現就是把protocol
和class
做一個映射,同時在內存中保存一張映射表,使用的時候,就通過protocol
找到對應的class
來獲取需要的服務。
protocol - class 架構圖如下:
調用方式解讀
注冊:
[ModuleManager registerClass:ClassA forProtocol:ProtocolA]
調用:
[ModuleManager classForProtocol:ProtocolA]
具體流程可參考如下:
創建中間件
1、創建 組件化的中間件manager,中間件 提供注冊機制的方法,通過字典存儲 procotol-class
2、中間件manager 提供 通過協議獲取class的方法
- (void)registServiceProvide:(id)provide forProcotol:(Protocol *)procotol;- (id)serviceProvideForProcotol:(Protocol *)procotol;
業務側提供協議 + 遵循協議的類
1、提供協議,協議中提供 需要的方法
@protocol ProductDetailServiceProcotol <NSObject>
- (UIViewController *)productDetailViewControllerWithProductId:(NSString *)productId;@end
2、創建類,類遵循協議,實現協議方法,方法內部提供想要的 代碼
3、創建的類中的+ (void)load
方法,內部 調用 中間件 注冊protocol-calss
@interface ProductDetailServiceProvide ()<ProductDetailServiceProcotol>@end@implementation ProductDetailServiceProvide//load 方法會在加載類的時候就被調用,也就是 ios 應用啟動的時候,就會加載所有的類,就會調用每個類的 + load 方法
+ (void)load
{[[ProcotolManager sharedManger] registServiceProvide:[[self alloc] init] forProcotol:@protocol(ProductDetailServiceProcotol)];
}#pragma mark - ProductDetailServiceProcotol- (UIViewController *)productDetailViewControllerWithProductId:(NSString *)productId
{ProcotolProductDetailViewController *detailVC = [[ProcotolProductDetailViewController alloc] init];detailVC.productId = productId;return detailVC;
}@end
調用
通過協議找到類,然后調用協議 方法「即:最終走的是 協議找到的那個class的方法」
id<ProductOrderServiceProcotol> servicePrivide = [[ProcotolManager sharedManger] serviceProvideForProcotol:@protocol(ProductOrderServiceProcotol)];UIViewController *orderVC = [servicePrivide productOrderWithProductId:self.productId];[self.navigationController pushViewController:orderVC animated:YES];
這種方案確實解決了方案一中無法傳遞非常規參數的問題,使得組件間的調用更為方便,但是它依然沒有解決組件依賴中間件的問題、內存中維護映射表的問題、組件的分散調用的問題。
方案三、target-action
重點詞: 分類
runtime
該方案 中間件是通過runtime來調用組件的服務,是真正意義上的解耦,也是該方案最核心的地方。具體實施過程是給組件封裝一層target對象來對外提供服務,不會對原來組件造成入侵;然后,通過實現中間件的category來提供服務給調用者,這樣使用者只需要依賴中間件,而組件則不需要依賴中間件。
調用方式解讀
創建中間件
中間件內部實現是通過runtime 拿到類和方法,進行調用的
@interface SYMediator : NSObject
+(instancetype)shareInstance;- (id)performTargetName:(NSString *)targetName actionName:(NSString *)actionName param:(NSDictionary *)dicParam;
@end
創建中間件的分類
分類中 的方法 調用 中間件的performTarget...
方法,來實現最終的方法調用。
#import "SYMediator+BookVC.h"static NSString *const kBookTarget = @"BookTarget";
static NSString *const kBookAction = @"bookVCWithParam";@implementation SYMediator (BookVC)
- (UIViewController *)bookViewControllerWithDicParam:(NSDictionary *)dicParm
{UIViewController *vc = [self performTargetName:kBookTarget actionName:kBookAction param:dicParm];if ([vc isKindOfClass:[UIViewController class]]) {return vc;} else {return [[UIViewController alloc] init];}
}
@end
創建target
組件封裝一層target對象來對外提供服務
@implementation BookTarget
- (UIViewController *)bookVCWithParam:(NSDictionary *)dicParm
{BookViewController *bookVC = [[BookViewController alloc] init];bookVC.bookName = dicParm[@"bookName"];bookVC.bookId = dicParm[@"bookid"];return bookVC;
}
@end
調用
NSDictionary *dicParm = @{@"bookName" : @"降龍十八掌",@"bookid" : @"sy0001"};//第一種方式(有category)UIViewController *bookVC = [[SYMediator shareInstance] bookViewControllerWithDicParam:dicParm];[self.navigationController pushViewController:bookVC animated:YES];
gitHub代碼鏈接參考
https://github.com/liyuunxiangGit/iOS-modularization