ios业务模块间互相跳转的解耦方案

原创 2016年03月30日 17:58:06

*此文章需有一点runtime的知识,如果你不了解runtime,《快速理解Runtime of Objective-C》:

http://mp.weixin.qq.com/s?__biz=MzIxNDI0OTAzOQ==&mid=403005635&idx=1&sn=71375cb0dee51487c90087d488ff59fe#rd


问题:


一个app通常由许多个模块组成,所有模块之间免不了会相互调用,例如一个读书管理软件,可能会有书架、用户信息、图书详情等等模块,从用户信息-我读的书中,可以打开图书详情。而在图书详情-所在书架,又可以打开书架。一般这种需求我们可能会这实现:


/*用户信息模块*/
#import "UserViewController.h"
#import "BookDetailViewController.h"

@implementation UserViewController
//跳转到图书详情
+ (void)gotoBookDetail {
    BookDetailViewController *detailVC = [[BookDetailViewController alloc] initWithBookId:self.bookId];
    [self.navigationController.pushViewController:detailVC animated:YES];
}
@end


项目初期还好,速度快,够简单。但是项目发展到一定程度时,问题就来了,每个模块都离不开其他模块,互相依赖粘在一起:



1:模块依赖关系,箭头方向表示依赖,比如:Discover依赖BookDetail


解决方案:


遇到这种情况,最直接的方法就是增加一个中间件,各个模块跳转通过中间件来管理。这样,所有模块只依赖这个中间件。但是中间件怎么去调用其他模块那?好吧,中间件又会依赖所有模块。好像除了增加代码的复杂度,并没有真正解决任何问题。


引入中间件的代码:


/*用户信息模块*/
#import "UserViewController.h"
#import "Mediator.h”

@implementation UserViewController
//跳转到图书详情
+ (void)gotoBookDetail {
    [Mediator gotoBookDetail:self.bookId];
}
@end

/*中间件*/
#import “Mediator.h”
#import “BookDetailViewController.h"

@implementation Mediator
//跳转到图书详情
+ (void)gotoBookDetail:(NSString *)bookid {
    BookDetailViewController *detailVC = [[BookDetailViewController alloc] initWithBookId:bookId];
    [self.navigationController.pushViewController:detailVC animated:YES];
}
@end

引入中间件的依赖关系:


图2:引入中间件的依赖关系


有没有一种方法,可以完美的解决这个依赖关系那?我们希望做到:每个模块之间互相不依赖,并且每个模块可以脱离工程由不同的人编写、单独编译调试。下面的方案通过对中间件的改造,很好的解决了这个问题,解决后的模块间依赖关系如下:


图3: 比较理想的模块间依赖关系


实现方法:


我们通过一个实际的例子来分析一下。

请先下载demo并打开:https://github.com/zcsoft/ZC_CTMediator


先看一下目录结构,对整个工程的组织结构有一个大致的了解,然后结合后面的结构图和每个类的说明、以及工程代码,来详细分析具体实现:



目录结构:


[CTMediator工程目录]

|-[CTMediator]

|        |-CTMediator.h.m

|        |-[Categories]

|                |-[ModuleA]

|                        |-CTMediator+CTMediatorModuleAActions.h.m

|-[DemoModule]

|        |-[Actions]

|        |       |-Target_A.h.m

|        |-DemoModuleADetailViewController.h.m

|-AppDelegate.h.m

|-ViewController.h.m



说明:


[CTMediator]

负责跳转的中间件,所有模块间跳转都通过这个模块来完成。


[DemoModule]

一个例子模块,假设我们要从其他业务(ViewController.h.m)中跳转到这个业务模块。


在这个demo中,我们的目的是从其他业务(ViewController.h.m中)跳转到DemoModule业务模块。



所有模块的引用关系如图:


图4:demo中个模块的引用关系

 

由于demo中只是从ViewController.h.m中跳转到DemoModule模块,所以只需要ViewController.h.m依赖CTMediator,CTMediator到DemoModule模块的调用是使用运行时完成了(图片中的蓝线),在代码中不需要相护依赖。也就是说,如果一个模块不需要跳转到其他模块,就不需要依赖CTMediator。



运行时的时序:


图5:隐藏了模块内实现细节的引用关系


调用关系概述:


首先由ViewController.h.m发起调用请求给CTMediator,CTMediator通过runtime去调用目标模块DemoModule,目标模块DemoModule根据参数创建自己的一个实例,并把这个实例返回给CTMediator,CTMediator在把这个实例返回给ViewController.h.m(此时ViewController.h.m不需要知道这个实例的具体类型,只需要知道是UIViewController的子类),然后由ViewController.h.m决定以什么样的方式去展示DemoModule。




图6: 完整的调用关系


调用关系详解:


1: ViewController.m发起调用请求给CTMediator(CTMediator+CTMediatorModuleAActions.m)。ViewController.m-57行


UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];


2: CTMediator+CTMediatorModuleAActions.m通过定义好的参数调用CTMediator,由于CTMediator+CTMediatorModuleAActions是CTMediator的扩展,所以可以直接使用self来调用CTMediator的实现。CTMediator+CTMediatorModuleAActions.m-行23

UIViewController *viewController =
        [self performTarget:kCTMediatorTargetA
                     action:kCTMediatorActionNativFetchDetailViewController
                     params:@{@"key":@"value"}];


3: CTMediator根据CTMediator+CTMediatorModuleAActions.m传过来的目标和参数发起实际调用。这个调用关系是在运行时完成的。所以此处并不需要在代码上依赖被调用者,如果被调用者不存在,也可以在运行时进行处理。CTMediator.m-93


return [target performSelector:action withObject:params];


4/5: Target_A创建一个DemoModuleADetailViewController类型的实例(这个实例是Target_A通过DemoModuleADetailViewController类的alloc/init创建的)。Target_A.m-20行


DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];


6: Target_A返回创建的实例到CTMediator.m(发起时是通过runtime,步骤3),返回后CTMediator.m并不知道这个实例的具体类型,也不会对这个类进行任何解析操作,所以CTMediator.m跟返回的实例之间是没有任何引用关系的。Target_A.m-23


7: CTMediator.m返回步骤6中得到的实例到CTMediator+CTMediatorModuleAActions.m(发起时是步骤2)。CTMediator.m-93行


8: CTMediator+CTMediatorModuleAActions.m会将步骤7返回的实例当作UIViewController处理,接下来会在运行时判断这个实例的类型是不是UIViewController(是不是UIViewController的子类)。然后将得到的UIViewController交给调用者ViewController.m(由ViewController.m负责以何种方式进行展示)。CTMediator+CTMediatorModuleAActions.m-行29



所有类的功能如下:


CTMediator.h.m

功能:指定目标(target,类名)+动作(action,方法名),并提供一个字典类型的参数。CTMediator.h.m会判断target-action是否可以调用,如果可以,则调用。由于这一功能是通过runtime动态实现的,所以在CTMediator.h.m的实现中,不会依赖任何其他模块,也不需要知道target-action的具体功能,只要target-action存在,就会被执行(target-action具体的功能由DemoModule自己负责)

CTMediator.h里实际提供了两个方法,分别处理url方式的调用和target-action方式的调用,其中,如果使用url方式,会自动把url转换成target-action。


CTMediator+CTMediatorModuleAActions.h.m

功能:CTMediator的扩展,用于管理跳转到DemoModule模块的动作。其他模块想要跳转到DemoModule模块时,通过调用这个类的方法来实现。

但是这个类中,并不真正去做跳转的动作,它只是对CTMediator.h.m类的封装,这样用户就不需要关心使用CTMediator.h.m跳转到DemoModule模块时具体需要的target名称和action名称了。


‘CTMediator.h.m’+‘CTMediator+CTMediatorModuleAActions.h.m’共同组成了一个面相DemoModule的跳转,并且它不会在代码上依赖DemoModule,DemoModule是否提供了相应的跳转功能,只体现在运行时是否能够正常跳转。至此,CTMediator这个中间层实现了完全的独立,其他模块不需要预先注册,CTMediator也不需要知道其他模块的实现细节。唯一的关联就是需要在‘CTMediator+CTMediatorModuleAActions.h.m’中写明正确的target+action和正确的参数,而且这些action和参数只依赖于Target_A.h.m。action和参数的正确性只会在运行时检查,如果target或action不存在,可以在‘CTMediator.h.m’中进行相应的处理。既:CTMediator不需要依赖任何模块就可以编译运行。


Target_A.h.m

提供了跳转到DemoModule模块的对外接口,与CTMediator+CTMediatorModuleAActions.h.m相互对应,可以说它只用来为CTMediator+CTMediatorModuleAActions.h.m提供服务,所以在实现CTMediator+CTMediatorModuleAActions.h.m时只需要参考Target_A.h.m即可,足够简单以至于并不需要文档来辅助描述。其他模块想跳转到这个模块时,不能直接通过Target_A.h.m实现,而是要通过CTMediator+CTMediatorModuleAActions.h.m来完成。这样,就实现了模块间相互不依赖,并且只有需要跳转到其他模块的地方,才需要依赖CTMediator。


DemoModuleADetailViewController.h.m

DemoModule模块的主视图,这个例子中,会从ViewController.h.m跳转到这个模块。


AppDelegate.h.m

APP入口,从应用外通过Scheme跳入程序时会经过这个类。


ViewController.h.m

APP主视图,需要在这里跳转到DemoModule模块。



@转载请包涵以下所有信息


参考资料:

1: 方案来自:http://casatwy.com/iOS-Modulization.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

感兴趣的前往阅读原文。

2: 本文同时也参考了:http://blog.cnbang.net/tech/3080/



欢迎大家关注我:iDevShare

或加我微信:lofocus


版权声明:本文为博主原创文章,未经博主允许不得转载。博主微信:lofocus

相关文章推荐

降低UIViewController切换的耦合

我们一般切换UIViewController的时候用的是如下代码 #import "UIViewControllerDemo.h" UIViewControllerDemo *vc = [UIVi...

CTMediator架构Demo学习

单例类(导航类):CTMediator开放方法如下:+ (instancetype)sharedInstance; // 远程App调用入口 - (id)performActionWithUrl:(N...

组件化架构漫谈

前段时间公司项目打算重构,准确来说应该是按之前的产品逻辑重写一个项目。在重构项目之前涉及到架构选型的问题,我和组里小伙伴一起研究了一下组件化架构,打算将项目重构为组件化架构。当然不是直接拿来照搬,还是...
  • args_
  • args_
  • 2016年10月09日 08:38
  • 681

中介者模式(Mediator Pattern)

定义 一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显示地相互引用,从而使其耦合性松散,而且可以独立地改变他们之间的交互。...

如何优雅地进行页面间的跳转(iOS)

在你的开发过程中,是否遇到过如下的需求: 在tableView类型的展示列表中,点击每个cell中人物头像都可以跳转到人物详情,可参见微博中的头像,同理包括转发、评论按钮、各种链接及linkca...
  • lxlzy
  • lxlzy
  • 2016年04月20日 16:06
  • 780

iOS项目组件化解耦

最近给公司的一个iOS项目进行组件化解耦。本身项目早期开发就不是很规范,而且刚刚开始熟悉这个项目对业务方面也不是很熟悉所以并没有对所有的模块进行组件化。而且组件化解耦后还存在一些问题在文章中都会写出来...
  • GGGHub
  • GGGHub
  • 2016年10月01日 11:06
  • 2814

iOS 工程解耦后 消息传递方式

根据上节的介绍,工程之间传递消息可以添加互相的依赖来实现,不过依赖所能解决的问题有一定的局限性, 如:Bu1 对Bu2 需要传递消息,可以添加Bu1对Bu2的依赖,但是如果同时Bu2对Bu1也要传递...

iOS多工程解耦

当项目比较大的时候,或者团队比较庞大的时候我们项目常常会采用组件化开发,主要是降低不同模块之间的耦合度,下面主要介绍一种代码解耦的方法。...

类似SCSF中EventBroker解耦事件调用方和接受方,打破"+="带来的耦合

今天在网上闲逛,偶然发现一个老外的开源组件,里面实现了微软SCSF框架中的EventBroker模块的功能,它的使用和SCSF几乎一样,熟悉SCSF的人几乎一下就能上手,只是这个类库不再像SCSF一样...
  • nodeman
  • nodeman
  • 2017年10月17日 10:50
  • 39

可复用且高度解耦的iOS用户统计实现

用户行为统计(User Behavior Statistics, UBS)一直是移动互联网产品中必不可少的环节,也俗称埋点。在保证移动端流量不会受较大影响的前提下,PM们总是希望埋点覆盖面越广越好。目...
  • lipnpn
  • lipnpn
  • 2016年05月03日 19:04
  • 293
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:ios业务模块间互相跳转的解耦方案
举报原因:
原因补充:

(最多只允许输入30个字)