iOS 组件化的方案

706 阅读4分钟

当 App 的功能逐渐庞大,开发人员越来越多的时候,就会出现各种各样的问题,比如:

  • 代码量庞大,编译速度慢
  • 功能交织,难以抽离,问题排插难度加大
  • 一处错误,导致整个项目难以运行

这时合理的组织架构就变得尤为重要。通常的做法就是将 App 内的功能进行模块化。使用模块化之后,合理化的模块化可以解决上面的问题:

  • 功能分成不同的模块,不同的人员维护不同的模块,责任清晰
  • 不经常改动的代码做成模块化之后,不需要经常编译,增加编译速度
  • 模块之间相互独立,减少依赖

组件化的方案

目前我了解到的组件化有三种方案:

方案一(面向接口)

通过面向接口的方式,各个模块通过接口来达成拆分和调用的目的。

但是这种方案有明显的缺点:调用者需要实现知道使用的模块,形式上代码分成了各个模块,但是各个模块之间依然有很强的依赖性,实质上依然没有被独立。

方案二(注册 url):

  1. 在 App 启动的时候,实例化各组件模块,然后这些组件向 ModuleManager 注册 url,有些不需要实例化的,使用 class 注册。

  2. 当组件 A 需要调用组件 B 的时候,想 ModuleManager 传递 url,参数跟随 url 以 GET 方式传递,类似 openURL,更复杂的参数需要通过专门的字典传递。然后由 ModuleManager 负责调度组件 B,最后完成任务。

这种方案有以下缺点:

  1. 在向 ModuleManager 注册 url 的时候,会造成内存常驻,特别是当注册的是实例化之后的类

  2. 在本地调用中,使用 url 的方式其实是不必要的。如果业务工程师在本地调度时给出 url,那么就不可避免的需要提供字典类的参数。而字典类的参数缺少编译器的检查,给调用者增加了难度,极易出错。

  3. URL 注册对于实施组件化的方案不是必要的,且通过 URL 注册的方式形成的组件化方案,拓展性和可维护性都会降低。具体来说:

    1. URL 的注册过程完全可以通过 runtime 的方式省略掉。当调用一个 URL 的时候,通过 runtime 去实例化对应的类就可以了。
    2. 当新增或者删除一些服务的时候,我们就需要不断的变更注册列表。使用 runtime 代替注册过程,也就是不需要维护这样一个注册列表了。

使用这种方案的框架有:

方案三(通过代码协议):

  1. 各个模块有约定好名字的类和方法来提供,不同的类名和方法名,提供不同的服务。
  2. 当通过中间件索要某种服务时,中间件通过 runtime 查找对应的类和方法,进行调用。
  3. 扩展中间件,提供各种服务的方法和参数。也就是将类名、方法名和参数名封装了起来,通过方法对外界提供。所以一个模块由两部分构成,一个是符合代码协议的类和方法,用来提供具体的服务,另一个对中间件的扩展,方便调用者。

使用这种方案的框架有:

  • CTMediator 3.2k star 是一个无需注册程序即可将 iOS 项目拆分成多个项目的中间件,它使用 Target-Action 模式使子项目相互通信,而不需要事先注册。

CTMediator 的实现

为什么 CTMediator 可以避免方案一中的问题呢?我们来拆解一下 CTMediator 的实现逻辑。

在 CTMediator 中,各个模块是通过 Target-Action 相互调用的,也就是通过提供服务的类名和方法名。通过名称的字符串来解决各个模块之间的耦合问题。

名称的字符串到具体的类和方法,则是通过 runtime 的特性,由字符串实例化类,由字符串获取到类的方法,从而进行调用。(这就避免了使用前的注册和由注册带来的问题。)

在使用某个服务时,如果需要使用服务名称的字符串,代码就会变得丑陋且难以维护。这时可以对中间件添加分类或者扩展,将服务对应的类名、方法名、参数名进行封装,对外提供一个方便的方法了。

参考资料

iOS应用架构谈 组件化方案