目的
1、学习组件化的使用
2、结合CTMediator作者博客进行源码解读
说明
1、部分关键描述会直接引用作者原话(可能会附注自己的理解),以免有失偏颇
2、学习讲究循序渐进,如有理解不完整,待以后有更多体会再来补充
设计思路
CTMediator是一个单例,主要是基于Mediator模式和Target-Action模式,中间采用了runtime来完成调用
源码分析
提供的API分别为:
远程app调用入口、本地组件调用入口、释放某个target缓存
// 远程App调用入口
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion;
// 本地组件调用入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
// 释放某个target缓存
- (void)releaseCachedTargetWithTargetName:(NSString *)targetName;
其中,以
本地组件调用入口最为核心,远程app调用入口是基于其上实现的,也就是本地组件调用入口是为远程app调用入口服务的
一、本地组件调用入口 performTarget: action: params: shouldCacheTarget: 实现分析
1、传入参数分别为:对象名称、动作名称、参数、是否缓存对象名称
-
对象名称:单独的组件/模块(repo)名。传入的targetName在CTMediator中会被拼接为字符串Target_targetName(可以理解为类名),然后通过NSClassFromString将字符串转为应的类。如:需要集成A模块。传入A,会被拼接成Target_A,然后被转换为[Target_AClass] -
动作名称:当拥有一个对象后,我们需要调用这个对象的方法来做一些事情,传入的actionName在CTMediator中会被拼接为字符串Action_actionName:(可以理解为方法名),然后通过NSSelectorFromString将字符串转为应的对象方法。如:模块A需要被push出来。传入isPushed,会被拼接成Action_isPushed:,然后被转换为[Action_isPushed:SEL] -
参数:当使用对象执行方法时,我们可能还需要一些参数来进行逻辑判断或往下传递,参数以NSDictionary对象传入 -
是否缓存Target对象:一个BOOL值。单例中维护着一个可变字典cachedTarget,目的是对初始化过的Target对象进行存储,字典中的key是字符串Target_targetName,value是Target对象
2、缓存及异常处理
- target:先去缓存中取,如果为空则创建;创建之后还为空,说明根据字符串
Target_targetName无法找到对应的类,则返回nil(实际开发过程中可以事先给一个固定的target,专门用于在这个时候顶上,然后处理这种请求的);根据BOOL值选择是否缓存Target对象 - action:通过
respondsToSelector:判断target对象是否响应action。如果响应了,则调用performSelector: withObject:去执行action方法,并返回该对象方法返回的值;如果没有响应,说明根据字符串Action_actionName:无法找到对应的对象方法,我们可以定义一个notFound:方法,如果可以响应,说明target对象中实现了这么一个方法,调用performSelector: withObject:去执行notFound:方法;如果target对象也不响应notFound:方法,删除cachedTarget中缓存的Target对象,并返回nil(也可以用一个固定的target顶上)
本地组件调用实现源码分析小结:
Target_targetName对应的是Target_A这个类,Action_actionName对应的是Target_A中的对象方法Action_isPushed:,参数(字典)放在该对象方法之后- 每一个
Target_targetName对应一个单独的业务,如Target_A对应AViewController,并且将它们放在同一个组件/模块(repo)下
二、本地组件调用实践
业务需求:点击Home页面中某个按钮,跳转到A页面,我们把A页面看作一个单独的业务,拿出来做成组件/模块调用
1、创建一个类Target_A,将AViewController与其放在同一个组件/模块下。在Target_A.h中声明- (UIViewController *)Action_isPushed:(NSDictionary *)params,在Target_A.m中引入AViewController.h,并实现方法Action_isPushed:,方法中创建AViewController对象,并返回该对象
2、通过创建CTMediator的Category(分类/类别)CTMediator+A,来增加针对A组件/模块(repo)的方法,CTMediator+A也是一个单独的组件/模块(repo)
3、在CTMediator+A中声明并实现- (UIViewController *)A_viewController:(NSDictionary *)params。在方法中调用performTarget: action: params: shouldCacheTarget:,传入参数分别为@”A”、@”isPushed”、params、NO,该方法调用后,正常情况下返回的是一个AViewController对象(内部实现:通过runtime最终调用Target_A对象中的Action_isPushed:方法,返回AViewController对象)
4、在我们原来引入AViewController.h的类中,将其替换为CTMediator+A,并使用[[CTMediator sharedInstance] A_viewController:params]替换[[AViewController alloc] init]来创建AViewController对象和传递参数
本地组件调用实践小结:
- 调用顺序:
CTMediator+A中通过A_viewController:调用performTarget: action: params: shouldCacheTarget:——> 在CTMediator中通过传入的targetName(A),找到Target_A对象,通过传入的actionName(isPushed:),找到Target_A对象中的对象方法Action_isPushed:——>Target_A对象调用Action_isPushed:方法,返回业务逻辑处理后的AViewController对象- 模块分工:
CTMediator+A决定调用哪个Target和Action,并将参数一并传递给CTMediator;CTMediator通过字符串拼接和runtime去找Target_A和对象方法Action_isPushed:,并使Target_A对象调用对象方法Action_isPushed:;Target_A在Action_isPushed:中通过接收到的参数处理各种业务逻辑,并返回AViewController对象
思考:既然
Categories和Modules是同一个维护者维护,为什么不放在同一个repo中管理呢?
- 作者原话:
A_Category Pod本质上只是一个方便方法,它对A Pod不存在任何依赖。Categories在实际应用中,是一个单独的repo,所用需要调度其他模块的人,只需要依赖这个repo(这个repo由target-action维护者维护);Modules是target-action所在的模块,也就是提供服务的模块,这也是单独的repo,但无需被其他人依赖,其他人通过category调用到这里的功能- 理解作者原话总结下:它们都是独立模块不需要相互依赖,所以放在不同repo中