iOS组件化
1. 组件化介绍
在一个项目越来越大,开发人员越来越多的情况下,项目会遇到很多问题。
- 业务模块间划分不清晰,模块之间耦合度很大,非常难维护。
- 所有模块代码都编写在一个项目中,测试某个模块或功能,需要编译运行整个项目。
为了解决这些问题,就需要用到组件化。组件化的作用如下:
- 模块间解耦
- 模块重用
- 提高团队协作开发效率
- 单元测试
并不是所有的项目都需要组件化,如果:
- 项目较小,模块间交互简单,耦合少
- 模块没有被多个外部模块引用,只是一个单独的小模块
- 模块不需要重用,代码也很少被修改
- 团队规模很小
那么这些项目就不需要组件化。
组件化最重要的部分就是颗粒度的划分,划分的越精准,组件化的效果就越好。组件化颗粒度的划分不是越小越好,颗粒度太小会增加通讯的成本。
一般来说组件化分层分为三层:
- 业务模块
- 通用模块
- 基础模块
这里一般从基础模块开始构建,然后构建通用模块,最后构建业务模块。
组件化需要注意以下几点:
- 只能上层对下层依赖
- 项目公共代码资源下沉
- 横向的依赖 最好下沉
2. CocoaPods
平时我们是如何把远程代码拉到本地的呢?这里需要一个东西去找到远程代码——CocoaPods
。这里以AFN
为例,可以在CocoaPods
找到AFN的文件,然后找到对应版本的json,并通过里面的信息来获得AFN
。
流程图:
3. 组件化操作
3.1 创建模块
在Terminal
中打开指定文件夹后输入 pod lib create 文件名
生成一个模块, 创建好后会自动打开一个工程。
接下来打开这个模块,在这个模块的classes
里面开始编写代码。
写好代码后,在进行一次pod install
操作,这样就可以看到之前创建的模块了。
3.2 第三方库和其他模块
如果文件依赖一些第三方库,那么就需要为其配置。
然后重新pod install
。
基础库也是一样的,但是需要在Podfile里面为其指定路径。
3.3 获取模块资源文件
如果需要用到其他模块的资源文件,那么就会找不到。那么要怎么做呢?
首先把要用到的图片放在对应模块的Assets里面。
然后在podspec
里面添加resource_bundles
后重新 pod install
。
然后就可以获取到相应的bundle获取图片。
获取json文件也是一样的。这里可以看到如果是正常的mainBundle
获取就会有问题。
根据相应的bundle
来获取json文件的话就没事了。
3.4 模块通讯
模块通讯三种方式:
url
路由target-action
protocol
匹配
url 路由
目前iOS上大部分路由工具都是基于URL
匹配的,或者是根据命名约定,用runtime方法进行动态调用
这些动态化的方案的优点是实现简单,缺点是需要维护字符串表,或者依赖于命名约定,无法在编译时暴露出所有问题,需要在运行时才能发现错误。
URL路由
方式主要是以蘑菇街
为代表的的 MGJRouter。
其实现思路是:
-
App启动时实例化各组件模块,然后这些组件向ModuleManager注册Url,有些时候不需要实例化,使用class注册
-
当组件A需要调用组件B时,向ModuleManager传递URL,参数跟随URL以GET方式传递,类似openURL。然后由ModuleManager负责调度组件B,最后完成任务。
// 1、注册某个URL
MGJRouter.registerURLPattern("app://home") { (info) in
print("info: \(info)")
}
//2、调用路由
MGJRouter.openURL("app://home")
URL 路由的优点
- 极高的动态性,适合经常开展运营活动的app,例如电商
- 方便地统一管理多平台的路由规则
- 易于适配URL Scheme
URl 路由的缺点
- 传参方式有限,并且无法利用编译器进行参数类型检查,因此所有的参数都是通过字符串转换而来
- 只适用于界面模块,不适用于通用模块
- 参数的格式不明确,是个灵活的 dictionary,也需要有个地方可以查参数格式。
- 不支持storyboard
- 依赖于字符串硬编码,难以管理,蘑菇街做了个后台专门管理。
- 无法保证所使用的的模块一定存在
- 解耦能力有限,url 的”注册”、”实现”、”使用”必须用相同的字符规则,一旦任何一方做出修改都会导致其他方的代码失效,并且重构难度大
target-action
这个方案是基于OC的runtime、category特性动态获取模块,例如通过NSClassFromString获取类并创建实例,通过performSelector + NSInvocation动态调用方法
其主要的代表框架是casatwy的 CTMediator。
其实现思路是:
- 利用分类为路由添加新接口,在接口中通过字符串获取对应的类
- 通过runtime创建实例,动态调用实例的方法
//******* 1、分类定义新接口
extension CTMediator{
@objc func A_showHome()->UIViewController?{
let params = [
kCTMediatorParamsKeySwiftTargetModuleName: "CJLBase_Example"
]
if let vc = self.performTarget("A", action: "Extension_HomeViewController", params: params, shouldCacheTarget: false) as? UIViewController{
return vc
}
return nil
}
}
//******* 2、模块提供者提供target-action的调用方式(对外需要加上public关键字)
class Target_A: NSObject {
@objc func Action_Extension_HomeViewController(_ params: