在看了不少其余的方案以后,首先对组件化思想上有一个小分歧。我认为不少人对于 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 中均可能很好的应用,而且解决大部分的业务需求。