标签:
看了limboy和Casa的文章,关于组件化开发,整理了一下思路。
一个产品,在最开始的时候,由于业务简单,一般是直接在一个工程里开发。这种方式,在产品起步阶段,是没有问题的,也能够有效的保证开发效率。但随着业务的不断发展,代码量不断增多,开发团队不断壮大,最后的模块间关系会发展成如下图所示:
从上图中可以看到,这种单一工程开发模式存在一些弊端:
- (void)gotoFileDetailVC {
OpenFileViewController *vc = [[OpenFileViewController alloc] initWithFileModel:model]; [self.navigationController pushViewController:vc animated:YES];
}
上面这种方式在初期没什么问题,但项目越来越大的时候,每个模块都离不开其他模块,互相依赖在一起。
从第一部分,我们知道单一工程开发模式下的问题,这里我们会用基于中间件的Target-Action方式,来对业务模块间的关系进行解耦,先看一下最终的结构图:
从上图可以看到:
但是从上图中,大家可能会提出两个问题:
这个问题的答案,其实在第一部分的为什么要做组件化说过了,这里再补充一句:
使用Runtime,除了让中间件没有了编译依赖,还能在运行时去判断处理组件不存在的情况,代码片段如下:
所以中间件是不依赖任何业务模块的,调用者和中间件模块可以单独使用。
看一下在业务模块A中直接使用Runtime调用业务模块B的代码:
Class cls = NSClassFromString(@"GofTargetBModule"); UIViewController *reviewVC = [cls performSelector:NSSelectorFromString(@"actionBViewCtrlWithParam:") withObject:@{@"title":@"模块B"}]; [self.navigationController pushViewController:reviewVC animated:YES];
这种调用方式存在几个问题:
现在我们从几个关键类的代码来看一下具体的实现细节。 下图是Demo的文件结构和流程图:
首先,是中间件类GofMediator,这个类的.h文件声明如下:
@interface GofMediator : NSObject + (instancetype)sharedInstance; /** * 远程调用 * * @param url url(统一格式示例:Gof://targetA/actionB?id=1234&name=LeeGof) * * @return YES/NO */ - (BOOL)performRemoteActionWithUrl:(NSURL *)url; /** * 本地调用 * * @param target 响应者 * @param action 动作 * @param params 参数 * * @return id类型 */ - (id)performNativeWithTarget:(NSString *)target action:(NSString *)action params:(NSDictionary *)params; @end
提供了一个远程调用和本地调用的方法,其中远程调用方式的URL,最好是固定一下格式,这样可以统一的解析和跳转。
接下来看一下调用方的代码,这样方便追根溯源。
UIViewController *vc = [[GofMediator sharedInstance] mediatorBViewController];
[self.navigationController pushViewController:vc animated:YES];
调用方用到了GofMediator的mediatorBViewController方法,继续看这个方法的实现:
- (UIViewController *)mediatorBViewController { UIViewController *viewController = [self performNativeWithTarget:@"B" action:@"BViewCtrlWithParam:" params:@{@"title": @"模块B"}]; if ([viewController isKindOfClass:[UIViewController class]]) { return viewController; } else //处理异常 { return nil; } }
可以看到,在上面方法的实现中,调用了GofMediator的本地调用方法来生成viewController,这个本地调用方法(文章上面贴出了该方法代码)是通过Runtime,最终调用GofTargetBModule的actionBViewCtrlWithParam方法。
- (UIViewController *)actionBViewCtrlWithParam:(NSDictionary *)param { GofBViewController *vc = [[GofBViewController alloc] initWithTitle:param[@"title"]]; return vc; }
在这里会最终生成GofBViewController实例并返回。
【总结】:这种方案,组件之间通过中间件进行通信,中间件通过 runtime 解耦调用业务组件的 target-action ,通过 category 分离各业务组件接口代码。
这是蘑菇街采用的方式,详情可以参考李忠的两篇文章。
和上面一样,我们先看看结构图:
通过上图,我们可以看到三点:
这是从结构图表面看到的,下面我们从代码角度来分析,从代码我们可以看到更多。下图是Demo的文件结构和流程图:
首先我们看一下注册URL的关键代码:
+ (void)initComponent { [[GofRouter sharedInstance] registerURLPattern:@"Gof://aViewController/:title" toHandler:^id(id param) { GofAViewController *vc = [[GofAViewController alloc] initWithTitle:param[@"title"]]; return vc; }]; }
这里,注册的效果是给GofRouter类的一个缓存字典,加入了一个URL和对应的回调函数。
接着我们看一下调用方的关键代码:
UIViewController *vc = [[GofRouter sharedInstance] openURL:@"Gof://aViewController/A模块" withParam:@{@"name": @"Gof"}]; [self.navigationController pushViewController:vc animated:YES];
从上面的描述,我们可以看到这种方式存在如下几个问题:
在实际项目中,进行组件化开发,我们需要考虑如下的问题:
第一个问题: 怎样拆分组件?
对于组件化开发,最重要的是对各业务模块进行组件化,当然,也需要考虑一些基础组件,那么在实际的项目中,组件化可以考虑这样来设计:
关于基础组件:
关于业务组件:
第二个问题:采用什么方式来实现组件之间的通信?
这个问题在第二部分内容中已经详细描述了两种方式来实现组件之间的通信,本人比较推荐第一种方式:基于中间件的Target-Action
第三个问题:怎样做代码的持续集成?
可以考虑采用submodule/subtree,也可以考虑使用Cocoapods的私有库来管理组件。
第四个问题:各端统一协调规范的问题。
一般很少有产品,在一开始就考虑组件化的问题。因为组件化是会带来开发成本的,当它的弊大于利的时候,我们通常会选择传统的单一工程开发模式,来快速开发出产品。随着产品的业务不断发展和快速迭代,开发团队规模的不断壮大,会暴漏前面提到的一些问题,这个时候我们会来思考一些改进方案,这就是组件化开发方案。但组件化开发方案,一方面是客户端需要统一设计和规划,那么相应的,其他各部门也要进行配合:
标签:
原文地址:http://www.cnblogs.com/LeeGof/p/5848683.html