YunsChou

既然选择了远方,便只顾风雨兼程

一名iOS开发者


欢迎你的访问

【组件化-1】CTMediator源码解读

目的

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_A Class]

  • 动作名称:当拥有一个对象后,我们需要调用这个对象的方法来做一些事情,传入的actionName在CTMediator中会被拼接为字符串Action_actionName:(可以理解为方法名),然后通过NSSelectorFromString将字符串转为应的对象方法。如:模块A需要被push出来。传入isPushed,会被拼接成Action_isPushed:,然后被转换为[Action_isPushed: SEL]

  • 参数:当使用对象执行方法时,我们可能还需要一些参数来进行逻辑判断或往下传递,参数以NSDictionary对象传入

  • 是否缓存Target对象:一个BOOL值。单例中维护着一个可变字典cachedTarget,目的是对初始化过的Target对象进行存储,字典中的key是字符串Target_targetNamevalueTarget对象

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决定调用哪个TargetAction,并将参数一并传递给CTMediatorCTMediator通过字符串拼接和runtime去找Target_A和对象方法Action_isPushed:,并使Target_A对象调用对象方法Action_isPushed:Target_AAction_isPushed:中通过接收到的参数处理各种业务逻辑,并返回AViewController对象

思考:既然CategoriesModules是同一个维护者维护,为什么不放在同一个repo中管理呢?

  • 作者原话:A_Category Pod本质上只是一个方便方法,它对A Pod不存在任何依赖。Categories在实际应用中,是一个单独的repo,所用需要调度其他模块的人,只需要依赖这个repo(这个repo由target-action维护者维护);Modules是target-action所在的模块,也就是提供服务的模块,这也是单独的repo,但无需被其他人依赖,其他人通过category调用到这里的功能
  • 理解作者原话总结下:它们都是独立模块不需要相互依赖,所以放在不同repo中
最近的文章

【组件化-2】组件化的配置及命令行的使用

一、组件的.podspec文件存放1、远程:在代码托管平台上新建空一个项目(YsPodSpec),用来存放组件的.podspec 文件2、本地:使用命令在本地创建一个仓库,并使该仓库指向远程仓库(YsPodSpec)pod repo add [本地repo名称] [远程仓库http地址]本地仓库(repo)的作用:用来存放本地组件工程的.podspec文件,将本地.podspec文件上传(push)到远程仓库二、组件工程文件/代码存放1、远程:在代码托管平台上新建一个空项目(YsModul...…

组件化继续阅读
更早的文章

【组件化-0】使自己的框架支持cocoapods

目的1、使自己的框架支持cocoapods2、使用pod管理组件化模块的准备知识说明1、从使用范围来看,主要分为两个方面: 供自己和团队使用(一般放在私有仓库中),pod引用时需要添加仓库路径 供其他开发者调用(框架提交至pod trunk),pod引用方式类似AFNetworking 注意:如果供自己和团队使用的代码放在GitHub的 公有仓库 中,那么外部开发者也是可以调用的,在调用时需要加上仓库路径 (本文即采用这样的方法,如果是内部使用,换成私有仓库即...…

组件化继续阅读