为何要使用组件化???

什么是组件化?

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

相关文章
相关标签/搜索