關于iOS組件化網上資料太多,這里只是從個人觀點說明一下怎么使用組件化和使用組件化的優點和缺點
首先下載CTMediatorDemo
Demo的目錄結構
項目在沒有使用CTMediator之前模塊間的關系是這樣的
當ModuleA要調用ModuleB和ModuleC的時候,需要#import ModuleB,ModuleC
同樣ModuleB要調用ModuleA和ModuleC的時候,需要#import ModuleA,ModuleC 這樣耦合程度非常嚴重
使用了CTMediator之后
ModuleA,ModuleB,ModuleC之間就沒有耦合了。
具體實現
CTMediator+ModuleA
#import "CTMediator.h"
NS_ASSUME_NONNULL_BEGIN
@interface CTMediator (ModuleA)
- (BaseViewController *)mediator_ModuleAPage1ViewController:(NSMutableDictionary *)params;
- (BaseViewController *)mediator_ModuleAPage2ViewController:(NSMutableDictionary *)params completion:(void (^)(NSDictionary *))completion;
@end
NS_ASSUME_NONNULL_END
#import "CTMediator+ModuleA.h"
// 字符串 是類名 Target_xxx.h 中的 xxx 部分
NSString * const kCTMediatorTarget_ModuleA = @"ModuleA";
// 字符串是 Target_xxx.h 中 定義的 Action_xxxx 函數名的 xxx 部分
NSString * const kCTMediatorActionNativTo_ModuleAPage1ViewController = @"ModuleAPage1ViewController";
NSString * const kCTMediatorActionNativTo_ModuleAPage2ViewController = @"ModuleAPage2ViewController";
@implementation CTMediator (ModuleA)
- (BaseViewController *)mediator_ModuleAPage1ViewController:(NSMutableDictionary *)params{
BaseViewController *viewController = [self performTarget:kCTMediatorTarget_ModuleA
action:kCTMediatorActionNativTo_ModuleAPage1ViewController
params:params shouldCacheTarget:NO];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之后,可以由外界選擇是push還是present
return viewController;
} else {
// 這里處理異常場景,具體如何處理取決于產品
NSLog(@"%@ 未能實例化頁面", NSStringFromSelector(_cmd));
return [[BaseViewController alloc] init];
}
}
- (BaseViewController *)mediator_ModuleAPage2ViewController:(NSMutableDictionary *)params completion:(void (^)(NSDictionary *))completion{
[params setValue:completion forKey:@"callback"];
BaseViewController *viewController = [self performTarget:kCTMediatorTarget_ModuleA
action:kCTMediatorActionNativTo_ModuleAPage2ViewController
params:params shouldCacheTarget:NO];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之后,可以由外界選擇是push還是present
return viewController;
} else {
// 這里處理異常場景,具體如何處理取決于產品
NSLog(@"%@ 未能實例化頁面", NSStringFromSelector(_cmd));
return [[BaseViewController alloc] init];
}
}
@end
Target_ModuleA
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Target_ModuleA : NSObject
- (BaseViewController *)Action_ModuleAPage1ViewController:(NSDictionary *)params;
- (BaseViewController *)Action_ModuleAPage2ViewController:(NSDictionary *)params;
@end
NS_ASSUME_NONNULL_END
#import "Target_ModuleA.h"
#import "ModuleAPage1ViewController.h"
#import "ModuleAPage2ViewController.h"
typedef void (^CallbackType)(NSDictionary *);
@implementation Target_ModuleA
- (BaseViewController *)Action_ModuleAPage1ViewController:(NSDictionary *)params{
ModuleAPage1ViewController *vc = [[ModuleAPage1ViewController alloc]init];
vc.parameter = params;
return vc;
}
- (BaseViewController *)Action_ModuleAPage2ViewController:(NSDictionary *)params{
CallbackType callback = params[@"callback"];
if (callback) {
callback(@{@"status":@"success"});
}
ModuleAPage2ViewController *vc = [[ModuleAPage2ViewController alloc]init];
vc.parameter = params;
return vc;
}
@end
ModuleAPage1ViewController
#import "ModuleAPage1ViewController.h"
@interface ModuleAPage1ViewController ()<UITableViewDelegate,UITableViewDataSource>
@property(strong, nonatomic) UITableView *tableView;
@property(strong, nonatomic) NSArray *dataArray;
@end
@implementation ModuleAPage1ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self setupTableView];
self.dataArray = @[@"ModuleAPage2",@"ModuleBPage1",@"ModuleCPage1"];
[self.tableView reloadData];
}
- (void)setupTableView{
self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIndetifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndetifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndetifier];
}
cell.textLabel.text = self.dataArray[indexPath.row];
return cell;;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44.f;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
switch (indexPath.row) {
case 0:
[self pushModuleAPage2];
break;
case 1:
[self pushModuleBPage1];
break;
case 2:
[self presentModuleCPage1];
break;
default:
break;
}
}
- (void)pushModuleAPage2
{
NSMutableDictionary *param = [NSMutableDictionary dictionary];
[param setValue:@"ModuleAPage1" forKey:@"page"];
UIViewController *viewController = [[CTMediator sharedInstance] mediator_ModuleAPage2ViewController:param completion:^(NSDictionary * _Nonnull dic) {
NSLog(@"%@",dic);
}];
[viewController setHidesBottomBarWhenPushed:YES];
[self.navigationController pushViewController:viewController animated:YES];
}
- (void)pushModuleBPage1
{
UIViewController *viewController = [[CTMediator sharedInstance] mediator_ModuleBPage1ViewController];
[viewController setHidesBottomBarWhenPushed:YES];
[self.navigationController pushViewController:viewController animated:YES];
}
- (void)presentModuleCPage1
{
UIViewController *viewController = [[CTMediator sharedInstance] mediator_ModuleCPage1ViewController];
[self.navigationController presentViewController:viewController animated:NO completion:nil];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
只粘貼一部分代碼,其余可以看Demo,
從ModuleAPage1ViewController中代碼可看出跳轉部分都沒有#import Module
這就是我們說的說的組件化。傳統方式是我們需要跳轉那個頁面,就需要#import相關的頁面,但是使用了CTMediator之后我們只要知道這個頁面是屬于那個Module,或者說屬于那個組件,然后直接調用相關的組件即可。
組件化的優點
舉個例子,公司某個App有一個登錄模塊,過一段時間需要研發一個新的App,為了節省時間就用之前App有的登錄模塊,就需要把登錄模塊抽取出來做成組件,可能某些同學就會問,這不是私有化Pod庫就能實現嗎?
接著說即使把登錄模塊私有化Pod后,那么假如在B項目中ModuleA,ModuleB,ModuleC都需要驗證,如果沒有登錄就調用登錄模塊,哪又回到了之前耦合的問題上了ModuleA,ModuleB,ModuleC都需要#import 登錄模塊,所以私有化只是組件的一部分。只有實現組件化才能解耦。
怎么判斷項目需要組件化
1.需要組件化首先就需要模塊化,就是對業務的高度抽象。需要把相關的業務都抽取到一個模塊里面。這就不適合創業公司或者只有一個開發人員的公司。小公司和創業公司基本都是在試錯,業務基本不穩定。高度抽象業務很難。如果在創業公司項目因業務不穩定,也不建議使用組件化。
2.公司如果只有一個開發人員也不建議使用組件化,組件化最大難度就是高度抽取業務,抽取出來組件化也需要維護,像我們公司一個人開發維護2個App哪就不要給自己找罪受了。
3.如果公司有2個以上開發人員并且時間相對充裕的情況下,在公司業務相對穩定,的情況下可以考慮使用組件化。
4.公司有2個及以上App,有重合的業務可以優先考慮先把重合業務抽取成組件。