iOS组件化开发技术探讨与实践

前言

iOS开发架构搭建要基于项目的规模,同时需要具有前瞻性。当前项目作为一个综合的管理系统,我们需要使整个工程具备可扩展性,可维护性。为此我们需要将各个功能模块进行抽离解耦。所谓的组件化,就是将一个应用的各个功能拆分为可以独立开发、测试、运行的模块,然后通过主工程进行拼装组合,最终构成完整的应用。

目前,国内的各种组件化(也叫模块化)都是对已有项目的重构,基本思路:

  1. 拆分各个功能模块,各个模块进行独立开发和测试;
  2. 使用CocoaPods或Carthage进行模块的管理;
  3. 主工程通过中间件进行模块间的通信。

下面我们根据这个基本思路进行技术可行性的探讨。

如何拆分功能模块

拆分粒度

虽然用组件化(Componentization)来描述这个过程,事实上我们是用写组件的方式来进行模块化(Modular)。一个模块对应的是功能单位,因此拆分的粒度不能像写一个控件那么精细。同时,要遵循单一功能原则,解除模块间的耦合,也不能让粒度过于粗大。粒度适中的拆分是组件化过程中第一道门槛。

下面我进行了简单的拆分:

iOS组件化开发技术探讨与实践_第1张图片
image
  • 将功能模块和业务模块进行拆分功能模块是指项目中的通用类、工具类和管理类等等,这部分模块并不涉及具体业务,而是在业务模块中进行调用。业务模块则是负责实现应用功能的模块。
  • 对功能模块进行拆分。这部分其实很难细分,扩展类(Extensions)存放系统类的扩展与封装;基础类和组件库存放这个项目使用的基础类和自定义控件(比如等待指示器、自定义弹窗);工具、管理类存放工具类和Manager(比如Network管理类,Cache管理类,Validation工具类等等)。
  • 对业务模块进行拆分。业务模块的拆分要遵循单一业务功能的原则,使不同模块间的耦合度最小化。另外,也要兼顾业务的完整性,过度拆分会造成模块散乱,增加维护成本。
  • 应避免存在无处拆分的文件,比如类似于Common这样的文件,应放在管理类或者对应的扩展中。对于会随业务变更的管理类(比如NetworkManager,API部分会受到业务影响),应该拆分为单独的模块。

模块间的通信

模块拆分之后,我们会形成如下依赖关系图:

iOS组件化开发技术探讨与实践_第2张图片
image
  1. 业务模块依赖功能模块;
  2. 功能模块依赖系统库或者第三方库;
  3. 业务模块直接依赖某些第三方库或系统库;
  4. 业务模块直接单向依赖、双向依赖。

如果我们允许业务模块之间直接通信,也就是模块间相互耦合,当某一模块修改时,难免会影响到别的模块。为了解耦,现在通用的做法就是引入中间件,通过中间件进行模块间的任务调度和数据传递。

下面是加入中间件后的依赖关系图:

iOS组件化开发技术探讨与实践_第3张图片
image

中间件实现方案

中间件作为调度中心,必须知道各个模块暴露的所有接口(本文中所说的接口,泛指对外开放的属性和方法),所以我们需要制定一个接口规范,每个模块按照这个规范来暴露自己的接口,中间件通过对应的接口进行模块间的通信。

我也查看了目前实现组件化的一些资料,总体上是两种方式:

  1. Limboy写的: 蘑菇街App的组件化之路 蘑菇街App的组件化之路.续, 通过Url进行页面跳转 和 通过Protocol进行组件间调用, MGJRouterModuleManager作为中间件进行调度管理。

    • 每个模块中都由一个Entry维护自己需要的URL,暴露该模块的对外接口,最终由MGJRouter调度;
    • URL无法解决的问题(比如传递非基本类型的参数),通过制定公共协议(Protocol),然后向ModuleManager注册实现了公共协议的类。
  2. Casa Taloyum 针对Limboy的文章提出自己的观点和方案:iOS应用架构谈组件化方案, 利用Target-Action方式,抽象出CTMediator作为中间件。

    • CTMediator通过Runtime中“NSSelectorFromString” 、“NSClassFromString”等方法,将String类型的Target和Action实例化调用的对象和方法。每个模块通过CTMediator的Category来暴露自己的接口。

下面比较了一下两个方案:

Url+Protocol Target-Action
编译影响 App启动时注册所有Url 运行时实例化响应者
维护成本 1. 需要维护模块本身的对外的接口,也需要像内部注册调用接口;2. 维护所有URL,并且要提供说明文档;3. 维护公共协议; 维护模块的CTMediator Category
使用成本 根据不同的场景,选择Url或Protocol不同的方式去实现,增加了上手难度;学习起来相对复杂,需要学习两个思路 部分字符串硬编码;手动引入需要的Category头文件;
耦合度 调用者需要注册待响应Url,中间件要对模块注册协议,个人认为解耦并不彻底,并没有使调用者彻底摆脱响应者。 Category充当了头文件角色,对外提供接口,调用者不需要任何额外代码去对应。

个人倾向于Target-Action的方式,因为进行模块的核心就是彻底解除模块的耦合。URL+Protocol的方式,仍然承担了一部分响应者要做的事情,比如方法实例化过程,Target-Action是下沉到了Category中完成,而URL+Protocol是在调用者或MiddleWare中完成的,解耦并不干净。

作为一个开发者,有一个学习的氛围和一个交流圈子特别重要,给大家推荐一个交流群,761407670(备注123),大家有兴趣可以进群里一起交流 学习!

你可能感兴趣的:(iOS组件化开发技术探讨与实践)