02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

lxf2023-11-03 22:30:02

02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

前言

先前一篇 文章 ,我们围绕 “移动客户端架构设计" 这个话题展开讨论的过程中,我们做了基本的综述,并且针对 架构设计 的大话题简扼地划分为五个部分:

  • 模块化开发
  • 组件化开发
  • 二进制化处理
  • 设计模式
  • 架构设计与架构演进 我们将在本篇文章,进一步对 模块化开发 这个话题 深入展开 讨论

我们先简单回顾一下,在前面的综述中介绍 移动端模块化开发 之前我们介绍的部分知识

项目周期简述

一个项目从立项到开发流程基本可以扼要概括为:

  1. 开发一个软件系统,首先需要确定需求
  2. 并基于需求对软件系统进行一定的分层设计
  3. 在层次划分的基础上进行模块划分
  4. 模块划分过后就是挑选组件用于集成实现模块
  5. 集成->开发->测试->部署上线......
  • 模块划分之后,我们需要做的工作就是模块选择模块设计
    • 模块选择1: 公司的现存开发套件的模块,基于已有模块的基础上进行适当改造迭代
    • 模块选择2: 采购 厂商的SDK 服务,如前面提及的 mPaaS 蚂蚁金融云服务等
    • 模块选择3: 去获取开源第三方模块,并基于此项目需求,进行适当改造
    • 模块选择4: 重新设计开发

模块化开发:

  • 【模块化开发】 指的是: 将一个系统或应用程序划分多个功能模块
  • 每个模块具有独立的功能接口,并且可以单独开发测试维护
  • 【模块化开发】目的是为了提高代码复用性可维护性可扩展性,以及降低开发测试的复杂度难度

组件化开发:

  • 【组件化开发】 则是在模块化开发的基础上,将模块进一步划分为更小的组件
  • 每个组件有自己的界面业务逻辑,并且可以独立部署和升级
  • 【组件化开发】 的目的是为了进一步提高代码复用性可维护性,同时还可以实现更多的代码并行开发更灵活的应用程序构建发布
  • 模块化开发组件化开发 都是为了提高软件开发的效率和质量
  • 组件化开发相对于模块化开发 更加细粒度灵活可以更好地满足大型应用程序/软件系统的的需求(换言之,一个模块可以集成若干组件,以实现模块的功能)
  • 个人总结,从设计上来看
    • 模块强调职责(内聚、分离)
    • 组件强调复用

模块设计:

无论是组件开发还是模块开发,我们都需要对其进行一定的设计工作,尽量避免不必要地重复工作和回避一些开发风险。

我们可以通过StarUML这个建模工具进行软件的设计工作,StarUML支持:

  • 类图时序图用例图等十几种图形模式;
  • 可以通过我 这篇文章初步了解StarUML
  • 可以通过 官网 进一步学习StarUML的使用

综述

本文介绍的内容适用于项目完成了需求确认,软件分层设计之后的阶段。

在完成了软件分层架构设计之后,需要将模块划分归类,进一步合理设计软件架构,并借助模块管理框架,实现App模块化开发以达到App的快速集成项目落地的技术方案。

本文内容框架

模块分类:
列举市面上流行的不同类型几款App,对模块进行划分

  • 按照【业务领域】划分;
  • 按照【功能服务】划分;
  • ......

模块划分
介绍模块划分的策略

  • 模块分类 是划分的结果
  • 模块划分 是分类的策略

了解几种模块化方案

  • 列举几种模块化方案
  • 分析不同方案的优劣

模块功能设计

  • 参考的模块化能力
  • 定制模块化能力
  • 理论分析
  • 设计模块功能

模块化开发实践

  • 开发模块管理框架
  • 开发模块,实践模块管理与集成

一、模块的三级分类

1. 第一级模块】: 应用级模块

以【金融-支付宝】为例分析:

模块划分:

  • 第一级模块】: 应用级模块:
    • 首页 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
    • 理财
    • 生活 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
    • 消息

2. 第二级模块】: 业务服务模块

以首页为例

  • 首页模块
    02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】 点击首页的更多入口,进入应用中心,我们可以看到若干业务功能模块,支付宝很好地为他们做了归类: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 【便民生活】:
    • 业务模块: 充值中心生活缴费交管12123...
  • 【购物娱乐】:
    • 业务模块: 饿了么彩票淘宝...
  • 【财富管理】:
    • 业务模块: 花呗借呗基金股票...
  • 【教育公益】:
    • 业务模块: 蚂蚁森林运动...

3. 第三级模块】: 功能模块

以便民生活大类的充值中心为例 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

  • 业务模块:充值中心
    • 业务1: 话费充值
    • 业务2: 流量充值
    • ...

我们经常使用支付宝,就不难发现, 几乎所有的 服务型 业务模块,都离不开一个功能,那就是 支付

此处 的 支付功能 就是 支付功能模块

支付宝的本质就是 移动支付工具 , 其它 商业性业务 都是 围绕其 即时、便捷的 电子支付功能 向广大用户 提供 生活中的各类服务的。

支付宝只有一个 功能模块

那如此说来,是不是支付宝就只有一个功能模块, 支付模块 呢?
答案必然是 否定 的!!!

支付宝实现其支付模块的功能,最起码实现了以下几个功能:

  • 网络通讯功能
  • 加密功能
  • 绑定银行卡,还用到了OCR识别功能、绑定验证时短信服务功能
  • 开启便捷支付时的人脸识别功能
  • 还有为了 监控 应用 运营情况, 到处都要用到的,埋点功能
  • ...

朋友们,若你还分不太清各级模块, 你也可以试着找一些应用来进行模块划分,欢迎留言,我们共同探讨!

其他案例分析推荐应用:

    1. 电商: 淘宝
    1. 教育: 中公教育、六分钟英语
    1. 医疗: 微医
    1. 车联网: 广汽传祺、哈啰出行&&嘀嗒出行
    1. 通讯: 中国移动
    1. 工具: AudioTools
    1. 娱乐: 抖音、网易云音乐
    1. 物联网: 海尔家居、华为手环
    1. 生活: 美团、菜鸟裹裹
  • ...

二、模块划分策略

1. 划分策略

我们从模块的三级分类案例中,可以总结得出结论:

  • 模块ke分三大类: 应用级模块业务级模块功能级模块
  • 应用级模块:
    个数取决于App本身的设计者,是采用由几大应用模块的丰富版 或是 单一应用模块的简约风格
    • 可 集成 若干 业务级模块
    • 必须集成 用户模块 :使用 用户数据进行 业务交互
    • 单应用模块App示例: 打车服务平台:嘀嗒出行 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
    • 多应用模块App示例: 打车服务平台:哈啰出行 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 业务级模块:
    • 应用级模块下,有若干个 业务级模块
    • 业务级模块示例: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
    • 业务级模块 业务模块即是 公司 商业活动的各种渠道
      • 用户的消费渠道(于企业主而言)
      • 广告渠道(于企业主而言)
      • 应用渠道(于用户而言)
      • .....
    • 其 实现, 至少依赖 一个 功能级模块
    • 其 实现, 需依赖 一定的业务UI组件
    • 其 实现, 可根据业务场景需要定制
      • 设计 业务服务
      • 设计 服务业务页面
      • 设计 场景交互
  • 功能级模块:
    • 小功能,自身可独立提供功能服务
    • 大功能,须依赖 若干小功能
    • 大功能 即是 小功能组

2. 常见的模块归类

按照【应用级】划分:

  • 支付宝:
    • 首页
    • 理财
    • 生活
    • 消息
    • 我的
  • 淘宝:
    • 首页
    • 逛逛
    • 消息
    • 购物车
    • 我的
  • 高德地图:
    • 首页
    • 附近
    • 消息
    • 打车
    • 我的
  • 美团:
    • 首页
    • 优选
    • 消息
    • 购物车
    • 我的
  • 抖音:
    • 首页
    • 朋友
    • 创作(符号模块:+号)
    • 消息
    • 我的
  • 哈啰出行:
    • 首页
    • 车主
    • 逛逛
    • 钱包
    • 我的
  • 嘀嗒出行:
    • 乘客应用模块
    • 车主应用模块
  • .......

按照【业务服务】划分:

  • 【业务模块】: 金融服务、电商服务、咨询业务、通讯业务、物联网业务等
    • 【金融业务】: 基金业务、理财业务、保险业务、存款业务、贷款业务、汇款业务等
    • 【电商业务】: 物流业务、下单业务、产品墙、商品详情展示与交互等
    • 【通讯业务】: 智能客服、音视频通讯、无人机操控等
  • 【物联网业务】.....
  • ....

按照【功能级】划分:

  • 【服务模块】:第三方平台服务、第二方平台服务、公司平台服务
    • LBS、社会化分享、广告服务、推送服务、即时通讯服务、埋点服务、电子支付服务等
  • 【加密模块】:
    • 公司方加密(前后端算法校验): 算法加密
      • MD5
      • RSA
      • AES
      • DES
      • GMx
      • Hash
      • ...
    • 二方加密平台服务(需采购): 证书加密、蓝牙Sim盾、Okta双重验证
    • 本地加密生物信息采集: 人脸识别、手势识别、面容识别
  • 【通讯模块】: 网络通信、无线物联网通讯
    • BLE通讯+iBeacon通讯、WIFI通讯等
    • https/http网络通讯、socket/websocket通讯等
  • 【硬件特性】: 拍摄功能、OCR识别功能、人脸识别功能、音视频功能、陀螺仪等传感器等
  • ......

3. 了解相关的技术概念以及命名规范约定

3.1 顶层概念

概念
Application应用程序: 模块化开发中我们用SubApp作为应用模块的关键词
Feature特性: 模块化开发feature作为业务模块的关键词。我们开发上线一个新业务模块常常也称之为新特性
Capability能力: 模块化开发capability作为功能模块的关键词
Component组件: 模块化开发component作为业务组件功能组件的关键词
Plugin插件: 模块化开发plugin作为二方库三方库公司平台库SDK服务/功能插件 的关键词。常见的服务:authentication(OAuth授权登录)、bot protection(防机器人爬虫)等
Foundation Librarieslow-level open source and commercial libraries

3.2 二级概念

Feature业务模块命名举例:
Finance金融业务: feature.finance
Commerce电商业务: feature.commerce
Life生活服务: feature.life
...XXX服务: feature.xxx

3.3 三级概念

Capability功能模块命名举例:
Live直播功能: capability.live
LBSLBS功能: capability.lbs
Network网络功能: capability.network
Persistence数据缓存功能: capability.persistence
Analytics埋点分析功能: capability.analytics
...XXX功能: capability.xxx

四级概念

关于组件的概念以及相关的划分管理规则,会在讲解组件的姊妹篇文章,进行深入阐述,本次不铺展开来介绍

Component组件命名举例:
Product carouselscomponent.productCarousels
Size pickerscomponent.sizePickers
Story cardscomponent.storyCards
...XXX组件: component.xxx

4. 小结

我们学高数的时候,总能听到教授念叨那几句华罗庚的经典语录:

  • 数缺形时少直观,形少数时难入微;
    数形结合百般好隔离分家万事休
    这句话的意思是, 直观的 图形映像 + 逻辑化的数学公式映像 相互结合 才能 更好地认识理解且相对记忆深刻。

4.1 架构分层图

所以,我们不妨在这里来搞一个 具象化 的 小结:

  • 依然以 支付宝为例: 三级模块划分的细节,同学们可以回到section1回顾一下 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 支付宝架构分层图: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
    • 应用层: 业务模块
    • 服务层: 业务服务功能模块
    • 容器层: 框架服务功能模块
    • 框架层: Native App FrameworkH5 App Framework 是属于蚂蚁团队 对组件封装 的中间层介质,为 服务层、应用层 提供 调用框架层的接口
  • 划分结构Tree:
    ·
    · - App
    ·    |
    · - - - - feature = [feature1 = Capability1 + Component1]+[feature2 = Capability1 + Component1]...
    ·            |
    · - - - - - - - - Capability、Component:  【Capability = Plugin + Capability实现封装】、【Capability = 纯Capability】、【Capability = Component+Component+Component+Component...+ Capability实现封装】
    ·
    

4.2 模块划分层级图:

02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

  • 应用结构分级结构:
    • 超级App
      • 业务模块1
        • 业务组件1
          • 【UI组件+功能模块】组合1
            • 【通用UI组件+功能模块】
          • 【UI组件+功能模块】组合2
            • 【定制化UI组件+功能模块】
              • 功能模块1
                • 插件1
                • 插件2
                • ...
              • 功能模块2
              • ...
          • ...
        • 业务组件2
        • ...
      • 业务模块2
      • ......

三、了解几种模块处理方案

1. 模块间有复杂的依赖关系:

一个 APP 有多个模块,模块之间会通信,互相调用;

  • 例如微信读书有 书籍详情 想法列表 阅读器 发现卡片 等等模块,这些模块会互相调用
  • 例如 书籍详情要调起阅读器和想法列表,阅读器要调起想法列表和书籍详情,等等,一般我们是怎样调用呢,以阅读器为例,会这样写:
    #import "WRBookDetailViewController.h" 
    #import "WRReviewViewController.h" 
    
    @implementation WRReadingViewController 
    - (void)gotoDetail { 
        WRBookDetailViewController *detailVC =  [[WRBookDetailViewController alloc] initWithBookId:self.bookId]; 
        [self.navigationController pushViewController:detailVC animated:YES]; 
    } 
    
    - (void)gotoReview { 
        WRReviewViewController *reviewVC = [[WRReviewViewController alloc] initWithBookId:self.bookId reviewType:1];
        [self.navigationController pushViewController:reviewVC animated:YES]; 
    }
    @end
    
  • 看起来挺好,这样做简单明了,没有多余的东西,项目初期推荐这样快速开发,但到了项目越来越庞大,这种方式会有什么问题呢?
  • 显而易见,每个模块都离不开其他模块
    相互引用,相互依赖,耦合严重: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 模块之间这样严重耦合,对测试/编译/开发效率/后续扩展都有一些坏处;

2. 模块间相互通讯的目的:

模块间相互通讯的目的,总结就以下几个:

  • 获取模块实例
    • 页面跳转
    • 页面传值
  • 使用模块的业务服务/功能服务
  • 传递响应者链信号
    • 捕获时机,执行相关逻辑
  • ...

3. 如何解耦呢?

按软件工程的思路,我们一看就知道应该加一个中间层

为了解决模块相互依赖耦合严重,广大码友们做过的一些尝试:

加了中间层后,一定程度摆脱解耦后的几种依赖模式:

  • 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

我们且称中间层Mediator,Mediator的职责就是模块间引用/通讯的目的。我们不妨进一步抽象总结之为,在实现不同模块解耦的前提下,达到:

  • 1、获取模块实例
  • 2、在模块间"消息传递"

那么这里有几个问题,我们需要回答清楚:

  1. Mediator 如何实现消息传递
  2. 模块A 只跟 Mediator通信,如何获取 模块B的接口?
  3. 模块间 的解耦 是通过Mediator 来完成的,模块Mediator 的互相依赖,如何削弱 甚至破除?

4. 经典解耦方案介绍

4.1 路由服务注册绑定模式:URL+HandlerBlock路由表管理

1.) 代表框架 MGJRouter
[MGJRouter工程目录]
|
|-[MGJRouter]
|        |-MGJRouter.h.m
|-[MGJRouterDemo]
|        |-[Actions]
|        |       |-Target_A.h.m
|        |-DemoModuleADetailViewController.h.m
|
|-AppDelegate.h.m
|-DemoDetailViewController.h.m
|-DemoListViewController.h.m
2.) 消息传递的中间件
  • [MGJRouter]
    MGJRouter是负责消息传递的中间件
  • 消息传递方式:
    • register url 注册模块
      • 通过 url 绑定 一个 操作绑定Block
      • url格式 = 协议头://模块/参数列表
    • open url 打开模块
  • 传递消息的中间件源码API:
    #import <Foundation/Foundation.h>
    
    extern NSString *const MGJRouterParameterURL;
    extern NSString *const MGJRouterParameterCompletion;
    extern NSString *const MGJRouterParameterUserInfo;
    
    /**
     *  routerParameters 里内置的几个参数会用到上面定义的 string
     */
    typedef void (^MGJRouterHandler)(NSDictionary *routerParameters);
    
    @interface MGJRouter : NSObject
    /**
     *  注册 URLPattern 对应的 Handler,在 handler 中可以初始化 VC,然后对 VC 做各种操作
     *
     *  **@param** URLPattern 带上 scheme,如 mgj://beauty/:id
     *  **@param** handler    该 block 会传一个字典,包含了注册的 URL 中对应的变量。
     *                    假如注册的 URL 为 mgj://beauty/:id 那么,就会传一个 @{@"id": 4} 这样的字典过来
     */
    + (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler;
    
    /**
     *  打开此 URL
     *  会在已注册的 URL -> Handler 中寻找,如果找到,则执行 Handler
     *
     *  **@param** URL 带 Scheme,如 mgj://beauty/3
     */
    + (void)openURL:(NSString *)URL;
    
    /**
     *  打开此 URL,同时当操作完成时,执行额外的代码
     *
     *  **@param** URL        带 Scheme 的 URL,如 mgj://beauty/4
     *  **@param** completion URL 处理完成后的 callback,完成的判定跟具体的业务相关
     */
    + (void)openURL:(NSString *)URL completion:(void (^)(void))completion;
    
    /**
     *  打开此 URL,带上附加信息,同时当操作完成时,执行额外的代码
     *
     *  **@param** URL        带 Scheme 的 URL,如 mgj://beauty/4
     *  **@param** parameters 附加参数
     *  **@param** completion URL 处理完成后的 callback,完成的判定跟具体的业务相关
     */
    + (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(void))completion;
    
    /**
     *  是否可以打开URL
     *
     *  **@param** URL
     *
     *  **@return**
     */
    + (BOOL)canOpenURL:(NSString *)URL;
    /**
     *  调用此方法来拼接 urlpattern 和 parameters
     *
     *  #**define MGJ_ROUTE_BEAUTY @"beauty/:id"**
     *  [MGJRouter generateURLWithPattern:MGJ_ROUTE_BEAUTY, @[@13]];
     *
     *
     *  **@param** pattern    url pattern 比如 @"beauty/:id"
     *  **@param** parameters 一个数组,数量要跟 pattern 里的变量一致
     *
     *  **@return**
     */
    
    + (NSString *)generateURLWithPattern:(NSString *)pattern parameters:(NSArray *)parameters;
    
    @end
    
3.) 小结:模块调用逻辑图+优点+缺点
3.1) 模块调用逻辑图:

02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

3.2) 优点:
  • 使用url-block的方案的确可以解决模块间相互引用,达到解耦
  • 蘑菇街专门用后台来管理路由url,统一管理
    • 解决了iOSAndroid的平台差异性 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
3.3) 缺点:
  • 需要有地方列出各个组件里有什么 URL 接口可供调用
    • 蘑菇街做了个后台专门管理 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 每个组件都需要初始化,内存里需要维护url-block映射表,组件多了会有内存问题
    // 注册一个组件(一般在启动的时候去注册)
        [MGJRouter registerURLPattern:@"mgj://detail?id=:id&name=:name" toHandler:^(NSDictionary *dic) {
            NSString * oneId = dic[@"id"];
            NSString * name  = dic[@"name"];
            if (oneId && name) {
                //创建组件,并从字典拿到值
                DetailComposite * detail = [[DetailComposite alloc] init];
                detail.oneId = oneId;
                detail.name  = name;
                // 执行组件的方法
                [detail showComposite];
            }
        }];
    
        // 外界去调用 执行一个组件
        [MGJRouter openURL:@"mgj://detail?id=5&name=leeDev"];
        // 打印出: showComposite _ id = 5 ; name = leeDev
    
  • 参数无固定格式,也需要有文档管理url入参说明
  • url的参数传递受到限制,只能传递常规的字符串参数,无法传递非常规参数,如UIImageNSData等类型
  • 没有区分本地调用远程调用的情况,尤其是远程调用,会因为url参数受限,导致一些功能受限
  • 组件本身依赖了中间件,且分散注册使的耦合较多
  • url硬编
  • ...

4.2 Protocol-Class服务注册绑定模式

1.) 代表框架

针对方案一的问题,蘑菇街又提出了另一种组件化的方案:

  • 就是通过protocol定义服务接口
  • 组件通过protocol接口,来访问实现接口的模块定义的服务:
    • 具体实现就是把protocolclass做一个映射
    • 同时在内存中保存一张映射表
    • 使用的时候,就通过protocol找到对应的class来获取需要的服务。
2.) 消息传递的中间件
  • [ModuleManager]
    ModuleManager是负责消息传递的中间件
  • 消息传递方式:
    • register Module 注册模块
      • 通过 protocol 提前绑定 一个 模块的实现 class
      • 公布 protocol 给所有模块
    • open Module 打开模块
      • 通过 protocol 获取 绑定的 class
      • 初始化class对应的组件进行使用
  • 传递消息的中间件源码API:
     /*
     // 注册
    [ModuleManager registerClass:ClassA forProtocol:ProtocolA]
     //调用
    [ModuleManager classForProtocol:ProtocolA]
     */
    
    @interface ModuleManager : NSObject
    + (void)registerClassName:(NSString *)className forProtocolName:(NSString *)protocolName;
    + (Class)classForProtocolName:(NSString *)protocolName;
    @end
    
    @interface ModuleManager()
    @property (nonatomic,strong) NSMutableDictionary * map;
    @end
    @implementation ModuleManager
    
    - (instancetype)init {
        if (self = [super init]) {
            self.map = [NSMutableDictionary dictionary];
        }
        return self;
    }
    
    + (ModuleManager *)shareManager {
        static dispatch_once_t onceToken;
        static ModuleManager * router = nil;
        dispatch_once(&onceToken, ^{
            if (router == nil) {
                router = [[ModuleManager alloc] init];
            }
        });
        return router;
    }
    
    + (void)registerClassName:(NSString *)className forProtocolName:(NSString *)protocolName {
        [self shareManager].map[protocolName] = className;
    }
    
    + (Class)classForProtocolName:(NSString *)protocolName {
        NSString * className = [self shareManager].map[protocolName];
        return NSClassFromString(className);
    }
    
    @end
    
3.) 小结:模块调用逻辑图+优点+缺点
3.1) 模块调用逻辑图

protocol-class模块调用逻辑图: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

设计思想和方案一类似,都是通过给组件加了一层wrapper:

3.2) 优点:
  • 解决了方案一中无法传递非常规参数的问题,使得组件间的调用更为方便
  • 结合方案1+方案2,可以区分本地调用远程调用的情况
  • 通过Protocol可以有效约束组件通讯时的传参个数,参数类型
  • plist映射表维护,代替内存中维护映射表,相对更方便管理 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 系统事件接入ModuleManager,进行管理
    • 系统的一些事件会有通知,比如 applicationDidBecomeActive 会有对应的 UIApplicationDidBecomeActiveNotification
    • 组件如果要做响应的话,只需监听这个系统通知即可
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
        NSArray *modules = [[ModuleManager sharedInstance] allModules];
        for (id module in modules) {
            if ([module respondsToSelector:_cmd]) {
                [module application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
            }
        }
    }
    
3.3) 缺点:
  • 依然没有解决组件依赖中间件的问题
  • 组件的分散调用的问题。分散调用依然会导致一定的耦合
  • 所有模块,依然得提前初始化
  • 对于 系统 事件的 管理,需要遍历全部已注册模块,项目体量大时,会有一定的性能损耗
  • 对于 模块 管理 欠缺归类 划分,只用一维key-Value关系,过于粗暴
  • ...

4.3 Target-Action反射模式:基于Runtime+OC反射机制+分类

1.) 代表框架 CTMediator
[CTMediator工程目录]
|
|-[CTMediator]
|        |-CTMediator.h.m
|        |-[Categories]
|                |-[ModuleA]
|                        |-CTMediator+CTMediatorModuleAActions.h.m
|
|-[DemoModule]
|        |-[Actions]
|        |       |-Target_A.h.m
|        |-DemoModuleADetailViewController.h.m
|
|-AppDelegate.h.m
|-ViewController.h.m
2.) 消息传递的中间件
  • CTMediator
    CTMediator是负责消息传递的中间件;
  • 消息传递方式:
    • 1、把需要开放给其它模块的API,封装操作到Target_XXXModule类
    • 2、通过给中间件CTMediator添加分类,借助分类调用当前模块公布的API
      • 调用的方式是借助Runtime里面的"Target-Action"反射机制
      • 避免对模块文件的引用
  • 传递消息的中间件源码API:
    // 远程App调用入口
    - (id _Nullable)performActionWithUrl:(NSURL * _Nullable)url completion:(void(^_Nullable)(NSDictionary * _Nullable info))completion;
    // 本地组件调用入口
    - (id _Nullable )performTarget:(NSString * _Nullable)targetName action:(NSString * _Nullable)actionName params:(NSDictionary * _Nullable)params shouldCacheTarget:(BOOL)shouldCacheTarget;
    // 释放 Target
    - (void)releaseCachedTargetWithFullTargetName:(NSString * _Nullable)fullTargetName;
    
    • 小结:
      • Target+Action+Params模式;
      • url模式(实际上也是【Target+Action+Params模式】,url解析之后就变成【Target+Action+Params】)
      • 返回值 为 id 类型
        • 可能是被调用的Target实例
        • 可能是被调用的nil
        • 可能是被调用的布尔值,代表Target-Action执行的结果
        • ...
  • DemoModule
    一个例子模块,假设我们要从其他业务(ViewController.h.m)中跳转到这个业务模块。

在这个demo中,我们的目的是从其他业务(ViewController.h.m中)跳转到DemoModule业务模块。

3.) 小结:模块调用时序图+优点+缺点
3.1) 模块调用运行时时序图:

02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】 调用关系概述:

  • 首先由ViewController.h.m发起调用请求给CTMediator
  • CTMediator通过runtime去调用目标模块DemoModule
  • 目标模块DemoModule根据参数创建自己的一个实例,并把这个实例返回给CTMediator
  • CTMediator再把这个实例返回给ViewController.h.m
  • (此时ViewController.h.m不需要知道这个实例的具体类型,只需要知道是UIViewController的子类),然后由ViewController.h.m决定以什么样的方式去展示DemoModule。

进一步细化DemoModule内部的调用逻辑(步骤4、5): 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

每个步骤的相关代码:

  • 步骤1:
    ViewController.m发起调用请求给CTMediator
    CTMediator通过分类CTMediator+CTMediatorModuleAActions.m暴露ModuleA的API
    UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];
    
  • 步骤2:
    CTMediator+CTMediatorModuleAActions.m通过定义好的参数调用CTMediator
    //由于CTMediator+CTMediatorModuleAActions是CTMediator的扩展,所以可以直接使用self来调用CTMediator的实现
    UIViewController *viewController =
            [self performTarget:kCTMediatorTargetA
                         action:kCTMediatorActionNativFetchDetailViewController
                         params:@{@"key":@"value"}];
    
  • 步骤3:
    CTMediator根据CTMediator+CTMediatorModuleAActions.m传过来的TargetActionparams发起实际调用。
    这个调用关系是通过 OC的运行时机制Runtime 完成的。
    所以此处并不需要在代码上依赖被调用者,如果被调用者不存在,也可以在运行时进行处理。
    return [target performSelector:action withObject:params];
    
  • 步骤4/5:
    Target_A创建一个DemoModuleADetailViewController类型的实例(这个实例是Target_A通过DemoModuleADetailViewController类的alloc/init创建的)。
    DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
    
  • 步骤6:
    Target_A返回创建的实例到CTMediator.m
    (发起时是通过runtime,步骤3),返回后CTMediator.m并不知道这个实例的具体类型,也不会对这个类进行任何解析操作,所以CTMediator.m跟返回的实例之间是没有任何引用关系的。
  • 步骤7:
    CTMediator.m返回步骤6中得到的实例到CTMediator+CTMediatorModuleAActions.m(发起时是步骤2)。
  • 步骤8:
    CTMediator+CTMediatorModuleAActions.m会将步骤7返回的实例当作UIViewController处理
    接下来会在运行时判断这个实例的类型是不是UIViewController(是不是UIViewController的子类)。
    然后将得到的UIViewController交给调用者ViewController.m(由ViewController.m负责以何种方式进行展示)
3.2) 所有类的功能:
  • CTMediator.h.m功能:

    • 指定目标(target,类名)+动作(action,方法名),并提供一个字典类型的参数。
    • CTMediator.h.m会判断target-action是否可以调用.如果可以,则调用
      • 由于这一功能是通过Runtime动态实现的,所以在CTMediator.h.m的实现中,不会依赖任何其他模块,也不需要知道target-action的具体功能
      • 只要target-action存在,就会被执行(target-action具体的功能由DemoModule自己负责)。
    • CTMediator.h里实际提供了两个方法:
      • 分别处理url方式的调用target-action方式的调用
      • 其中,如果使用url方式,会自动把url转换成target-action。
  • CTMediator+CTMediatorModuleAActions.h.m功能:

    • CTMediator的扩展,用于管理跳转到DemoModule模块的动作。
    • 其他模块想要跳转到DemoModule模块时,通过调用这个类的方法来实现。
    • 但是这个类中,并不真正去做跳转的动作,它只是对CTMediator.h.m类的封装,
    • 这样开发者就不需要关心使用CTMediator.h.m跳转到DemoModule模块时具体需要的target名称和action名称了。
    • CTMediator.h.mCTMediator+CTMediatorModuleAActions.h.m共同组成了一个面向DemoModule的跳转,并且它不会在代码上依赖DemoModuleDemoModule是否提供了相应的跳转功能,只体现在运行时是否能够正常跳转。
    • 至此,CTMediator这个中间层实现了完全的独立,其他模块不需要预先注册,CTMediator也不需要知道其他模块的实现细节。
    • 唯一的关联就是需要CTMediator+CTMediatorModuleAActions.h.m中写明正确的targetaction正确的参数
      • 而且这些action和参数只依赖于Target_A.h.m
      • action参数的正确性只会在运行时检查
      • 如果targetaction不存在,可以在CTMediator.h.m中进行相应的处理。
      • 即:CTMediator不需要依赖任何模块就可以编译运行。
  • Target_A.h.m

    • 提供了跳转到DemoModule模块的对外接口
    • CTMediator+CTMediatorModuleAActions.h.m相互对应,可以说它只用来为CTMediator+CTMediatorModuleAActions.h.m提供服务,所以在实现CTMediator+CTMediatorModuleAActions.h.m时只需要参考Target_A.h.m即可,足够简单以至于并不需要文档来辅助描述。
    • 其他模块想跳转到这个模块时,不能直接通过Target_A.h.m实现,而是要通过CTMediator+CTMediatorModuleAActions.h.m来完成。这样,就实现了模块间相互不依赖,并且只有需要跳转到其他模块的地方,才需要依赖CTMediator
3.3) 优点:
  • 模块通过中间件解析,再由中间件发送消息通讯
  • 中间件通过 runtime 接口解耦
  • 通过 target-action 简化写法,调用简单方便
  • 代码自动补全编译时检查都仍然有效
  • 通过 category 感官上分离组件接口代码
3.4) 缺点:
  • Category 存在重名覆盖的风险,需要通过开发规范以及一些检查机制来规避
  • 解决 模块间 相互 引用 耦合严重的 问题, 但 一套API 需要一定的重复的封装,对编程提效,不是很友好
    • 每增加一个模块,就要补充一个Category实现,一个TargetModule实现,模块多了,这个得想办法适当削减
    • 模块A 想要调用 模块B 的一套 API,模块B就得自己模块内部实现一遍,且通过一定的方式 暴露给 模块A, 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • Mediator 内部存在一定的参数硬编
    • 项目体量大的时候,具有一定的 维护性 难度
    • 对开发效率有一定影响
    • 并不友好,且写法欠优雅
  • 命名规则并不美观且相对复杂:每个Target都得加Target_A,每个Action都得加Action_
  • 调用 Mediator API 的 返回值 是什么 无法不明确知晓,很不友好,还得编写一定的类型检查代码
  • ...

4.4 模块服务管理工具:模块服务注册+URL-HandlerBlock路由注册表方案

02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

1.) 代表框架 :BeeHive
[BeeHive工程目录]
|
|-[BeeHive]
|        |-BHAnnotation.h.m
|        |-BHModuleProtocol.h
|        |-BHModuleManager.h.m
|        |
|        |-BHRouter.h.m
|        |
|        |-BHServiceProtocol.h
|        |-BHServiceManager.h.m
|        |
|        |-BHAppDelegate.h.m
|        |-BHContext.h.m
|        |-BHConfig.h.m
|        |-BeeHive.h.m
|        |-BeeHive.bundle
|        |      |  - BeeHive.plist
|        |      |  - BHService.plist
|        |
|        |-BHTimeProfiler.h.m
|        |-BHWatchDog.h.m
|        |
|        |- BHDefines.h
|        |- BHCommon.h
|        |
2.) 消息传递的中间件
  • BeeHive
    • BHModuleManager是负责 本应用内消息传递 的中间件;
    • BHRouter是负责 跨应用服务消息传递 的中间件;
  • 消息传递方式:
    通过处理Event编写各个业务模块可以实现插件化编程
    各业务模块之间没有任何依赖,coremodule之间通过event交互,实现了插件隔离。
    1. 基于接口的实现Service访问方式(Java spring框架实现)
      • BHModuleManager
        • 消息分类+模块消息定义+模块注册+模块调用
          • BeeHive模块管理工具把事件划分为三类进行管理:
            系统级事件应用级事件业务自定义事件
            • 系统事件通常是Application生命周期事件
            • 系统事件的基础之上,扩展了应用的通用事件
              例如modSetupmodInit等,可以用于编码实现各插件模块的设置与初始化
            • 如果觉得系统事件通用事件不足以满足需要,可以通过继承 BHAppdelegate来扩展当前应用的业务自定义事件BHAppdelgate内原本就封装了对系统事件、通用事件的处理)
          • 模块注册之后就可以 给 其它模块 进行调用
          • 可以通过给 模块管理中间件BHModuleManager 发送 另一个模块的消息调用另一个模块
          • 也可以通过 [[BeeHive shareInstance] createService:@protocol(XXXServiceProtocol)] 直接获取一个服务
    2. 基于跨应用实现的URL Route模式(iPhone App之间的互访)
      • BHRouter 类似于 蘑菇街 的URL-HandlerBlock方案
      • 支持App间互访,也支持Hybrid开发时,通过锻炼访问web业务包(类似于小程序技术)
2.1) 了解其框架设计

1、实现特性

  • 插件化的模块开发运行框架
  • 模块具体实现与接口调用分离
  • 模块生命周期管理,扩展了应用的系统事件

2、设计原则

  • 因为基于SpringService理念,虽然可以使模块间的具体实现与接口解耦,但无法避免对接口类的依赖关系。
  • 为什么不使用invoke以及动态链接库技术实现对接口实现的解耦,类似ApacheDSO的方式?
    • 主要是考虑学习成本难度
    • 以及动态调用实现无法在编译检查阶段检测接口参数变更等问题
    • 动态技术需要更高的编程门槛要求。

3、项目名来源

  • BeeHive灵感来源于蜂窝
  • 蜂窝是世界上高度模块化的工程结构,六边形的设计能带来无限扩张的可能
  • 所以作者用了BeeHive来做为这个项目的命名

4、模块生命周期的事件

BeeHive会给每个模块提供生命周期事件,用于与BeeHive宿主环境进行必要信息交互,感知模块生命周期的变化。

2.2)了解框架事件分层

事件分为三种类型:

  • 系统事件
  • 通用事件
  • 业务自定义事件

1、系统事件

系统事件通常是Application生命周期事件,例如DidBecomeActiveWillEnterBackground等。

系统事件基本工作流如下: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

2、通用事件

在系统事件的基础之上,扩展了应用的通用事件,例如modSetupmodInit等,可以用于编码实现各插件模块的设置与初始化。

扩展的通用事件如下: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

3、业务自定义事件

如果觉得系统事件、通用事件不足以满足需要,作者还将事件封装简化成BHAppdelgate,你可以通过继承 BHAppdelegate来扩展自己的事件。

2.3) 模块注册方式

模块注册的方式有静态注册与动态注册两种。

1、 静态注册

通过在BeeHive.plist文件中注册符合BHModuleProtocol协议模块类: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】

2、 动态注册

@implementation HomeModule
BH_EXPORT_MODULE() // 声明该类为模块入口
@end

在模块入口类实现中 使用BH_EXPORT_MODULE()宏声明该类为模块入口实现类。

3、异步加载模块

如果设置模块导出为BH_EXPORT_MODULE(YES),则会在启动之后第一屏内容展现之前异步执行模块的初始化,可以优化启动时时间消耗。

2.4) 编程开发应用

BHModuleProtocol为各个模块提供了每个模块可以Hook的函数,用于实现插件逻辑以及代码实现。

1、设置环境变量

通过context.env可以判断我们的应用环境状态来决定我们如何配置我们的应用。

-(void)modSetup:(BHContext *)context{
    switch (context.env) {
        case BHEnvironmentDev:
        //....初始化开发环境
        break;
        case BHEnvironmentProd:
        //....初始化生产环境
        default:
        break;
    }
}

2、模块初始化

如果模块有需要启动时初始化的逻辑,可以在modInit里编写,例如模块注册一个外部模块可以访问的Service接口

-(void)modInit:(BHContext *)context {
    //注册模块的接口服务
    [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}

3、处理系统事件

系统的事件会被传递给每个模块,让每个模块自己决定编写业务处理逻辑,比如3D-Touch功能

-(void)modQuickAction:(BHContext *)context{
    [self process:context.shortcutItem handler:context.scompletionHandler];
}

4、模间调用

通过处理Event编写各个业务模块可以实现插件化编程,各业务模块之间没有任何依赖,coremodule之间通过event交互,实现了插件隔离。但有时候我们需要模块间的相互调用某些功能来协同完成功能。

框架提供了两种形式的接口访问形式:

  1. 基于接口的实现Service访问方式(Java spring框架实现)
  2. 基于跨应用实现的URL Route模式(iPhone App之间的互访)

5、 定义接口

以为HomeServiceProtocol为例。

@protocol HomeServiceProtocol <NSObject, BHServiceProtocol>
- (void)registerViewController:(UIViewController *)vc title:(NSString *)title iconName:(NSString *)iconName;
@end

6、注册Service

有两种方式:

  • API注册
    [[BeeHive shareInstance] registerService:@protocol(HomeServiceProtocol) service:[BHViewController class]];
    
  • BHService.plist注册
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    
    <plist version="1.0">
    <dict>
        <key>HomeServiceProtocol</key>
        <string>BHViewController</string>
    </dict>
    </plist>
    

7、调用Service

#import "BHService.h"

id< HomeServiceProtocol > homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];

8、单例与多例

对于有些场景下,我们访问每个声明Service对象,希望对象能保留一些状态,那我们需要声明这个Service对象是一个单例对象。

我们只需要在Service对象中实现事件函数声明

-(BOOL) singleton{
    return YES;
}

通过createService获取的对象则为单例对象,如果实现上面函数返回的是NO,则createService返回的是多例。

id< HomeServiceProtocol > homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];

9、上下文环境Context

  • 初始化设置应用的项目信息,并在各模块间共享整个应用程序的信息
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [BHContext shareInstance].env = BHEnvironmentDev; //定义应用的运行开发环境
        [BHContext shareInstance].application = application;
        [BHContext shareInstance].launchOptions = launchOptions;
        [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/CustomModulePlist";//可选,默认为BeeHive.bundle/BeeHive.plist
        [BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/CustomServicePlist";//可选,默认为BeeHive.bundle/BHService.plist
    
        [[BeeHive shareInstance] setContext:[BHContext shareInstance]];
    
        [super application:application didFinishLaunchingWithOptions:launchOptions];
    
        id<HomeServiceProtocol> homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
    
        if ([homeVc isKindOfClass:[UIViewController class]]) {
            UINavigationController *navCtrl = [[UINavigationController alloc] initWithRootViewController:(UIViewController*)homeVc];
            self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
            self.window.rootViewController = navCtrl;
            [self.window makeKeyAndVisible];
        }
    
        return YES;
    }
    
3.) 小结:工作流图+架构图+核心类关系图+优点+缺点
3.1) 工作流图+架构图+核心类关系图
  • 系统事件基本工作流:
    02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 扩展的通用事件+系统事件基本工作流:
    02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 基本架构图:
    02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • BeeHive详细架构图:
    02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 核心类关系图:
    02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
3.2) 所有类的功能如下:
  • BHModuleManager
    • 负责所有事件(系统级事件应用级事件业务自定义事件)的消息传递
    • BHModuleManager对外公布的API 如下:
      //模型等级,用于分层设计
      typedef NS_ENUM(NSUInteger, BHModuleLevel){
          BHModuleBasic  = 0,
          BHModuleNormal = 1
      };
      
      typedef NS_ENUM(NSInteger, BHModuleEventType){
          /// #pragma mark - 1-通用应用级事件
          BHMSetupEvent = 0,
          BHMInitEvent,
          BHMTearDownEvent,
          BHMSplashEvent,
      
          /// #pragma mark - 2-系统级事件
          BHMQuickActionEvent,
          BHMWillResignActiveEvent,
          BHMDidEnterBackgroundEvent,
          BHMWillEnterForegroundEvent,
          BHMDidBecomeActiveEvent,
          BHMWillTerminateEvent,
      
          BHMUnmountEvent,
      
          // 应用交互
          BHMOpenURLEvent,
          // 内存警告
          BHMDidReceiveMemoryWarningEvent,
          // 本地推送、远程推送
          BHMDidFailToRegisterForRemoteNotificationsEvent,
          BHMDidRegisterForRemoteNotificationsEvent,
          BHMDidReceiveRemoteNotificationEvent,
          BHMDidReceiveLocalNotificationEvent,
          BHMWillPresentNotificationEvent,
          BHMDidReceiveNotificationResponseEvent,
      
          // Widget事件
          BHMWillContinueUserActivityEvent,
          BHMContinueUserActivityEvent,
          BHMDidFailToContinueUserActivityEvent,
          BHMDidUpdateUserActivityEvent,
          // 手表事件
          BHMHandleWatchKitExtensionRequestEvent,
      
          /// #pragma mark - 3-业务自定义事件
          BHMDidCustomEvent = 1000
      
      };
      
      @class BHModule;
      
      @interface BHModuleManager : NSObject
      + (instancetype)sharedManager;
      
      // If you do not comply with set Level protocol, the default Normal
      - (void)registerDynamicModule:(Class)moduleClass;
      - (void)registerDynamicModule:(Class)moduleClass
             shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent;
      - (void)unRegisterDynamicModule:(Class)moduleClass;
      
      - (void)loadLocalModules;
      - (void)registedAllModules;
      
      - (void)registerCustomEvent:(NSInteger)eventType
               withModuleInstance:(id)moduleInstance
                   andSelectorStr:(NSString *)selectorStr;
      
      - (void)triggerEvent:(NSInteger)eventType;
      - (void)triggerEvent:(NSInteger)eventType 
           withCustomParam:(NSDictionary *)customParam;
      
      @end
      
    • BHModuleManager通过调用triggerEventAPI来处理所有的消息事件传递:
      - (void)triggerEvent:(BHModuleEventType)eventType {
          switch (eventType) {
              case BHMSetupEvent:
                  [self handleModuleEvent:kSetupSelector];
                  break;
              case BHMInitEvent:
                  //special
                  [self handleModulesInitEvent];
                  break;
              case BHMTearDownEvent:
                  //special
                  [self handleModulesTearDownEvent];
                  break;
              case BHMSplashEvent:
                  [self handleModuleEvent:kSplashSeletor];
                  break;
              case BHMWillResignActiveEvent:
                  [self handleModuleEvent:kWillResignActiveSelector];
                  break;
              case BHMDidEnterBackgroundEvent:
                  [self handleModuleEvent:kDidEnterBackgroundSelector];
                  break;
              case BHMWillEnterForegroundEvent:
                  [self handleModuleEvent:kWillEnterForegroundSelector];
                  break;
              case BHMDidBecomeActiveEvent:
                  [self handleModuleEvent:kDidBecomeActiveSelector];
                  break;
              case BHMWillTerminateEvent:
                  [self handleModuleEvent:kWillTerminateSelector];
                  break;
              case BHMUnmountEvent:
                  [self handleModuleEvent:kUnmountEventSelector];
                  break;
              case BHMOpenURLEvent:
                  [self handleModuleEvent:kOpenURLSelector];
                  break;
              case BHMDidReceiveMemoryWarningEvent:
                  [self handleModuleEvent:kDidReceiveMemoryWarningSelector];
                  break;
      
              case BHMDidReceiveRemoteNotificationEvent:
                  [self handleModuleEvent:kDidReceiveRemoteNotificationsSelector];
                  break;
      
              case BHMDidFailToRegisterForRemoteNotificationsEvent:
                  [self handleModuleEvent:kFailToRegisterForRemoteNotificationsSelector];
                  break;
              case BHMDidRegisterForRemoteNotificationsEvent:
                  [self handleModuleEvent:kDidRegisterForRemoteNotificationsSelector];
                  break;
      
              case BHMDidReceiveLocalNotificationEvent:
                  [self handleModuleEvent:kDidReceiveLocalNotificationsSelector];
                  break;
      
              case BHMWillContinueUserActivityEvent:
                  [self handleModuleEvent:kWillContinueUserActivitySelector];
                  break;
      
              case BHMContinueUserActivityEvent:
                  [self handleModuleEvent:kContinueUserActivitySelector];
                  break;
      
              case BHMDidFailToContinueUserActivityEvent:
                  [self handleModuleEvent:kFailToContinueUserActivitySelector];
                  break;
      
              case BHMDidUpdateUserActivityEvent:
                  [self handleModuleEvent:kDidUpdateContinueUserActivitySelector];
                  break;
      
              case BHMQuickActionEvent:
                  [self handleModuleEvent:kQuickActionSelector];
                  break;
      
              default:
                  [BHContext shareInstance].customEvent = eventType;
                  [self handleModuleEvent:kAppCustomSelector];
                  break;
          }
      }
      
    • 1.1 系统事件管理:
      • BHAppDelegate
        引入BHAppDelegate接管 系统事件
        把原本系统事件回调监听(也即是原本Appdelegate遵守的UIApplicationDelegate协议方法)纳入模块事件管理:
      • 系统事件通常是Application生命周期事件,例如DidBecomeActiveWillEnterBackground
      • 系统事件参数都被当作环境变量缓存了起来,所有模块共享
      • 系统事件基本工作流如下: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
      • 系统事件消息传递源码:
        - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
        {
        
            [[BHModuleManager sharedManager] triggerEvent:BHMSetupEvent];
            [[BHModuleManager sharedManager] triggerEvent:BHMInitEvent];
        
            dispatch_async(dispatch_get_main_queue(), ^{
                [[BHModuleManager sharedManager] triggerEvent:BHMSplashEvent];
            });
        #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
            if ([UIDevice currentDevice].systemVersion.floatValue >= 10.0f) {
                [UNUserNotificationCenter currentNotificationCenter].delegate = self;
            }
        #endif
        
        #ifdef DEBUG
            [[BHTimeProfiler sharedTimeProfiler] saveTimeProfileDataIntoFile:@"BeeHiveTimeProfiler"];
        #endif
            return YES;
        }
        
        
        #if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400 
        
        -(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
        {
            [[BeeHive shareInstance].context.touchShortcutItem setShortcutItem: shortcutItem];
            [[BeeHive shareInstance].context.touchShortcutItem setScompletionHandler: completionHandler];
            [[BHModuleManager sharedManager] triggerEvent:BHMQuickActionEvent];
        }
        #endif
        
        - (void)applicationWillResignActive:(UIApplication *)application
        {
            [[BHModuleManager sharedManager] triggerEvent:BHMWillResignActiveEvent];
        }
        
        - (void)applicationDidEnterBackground:(UIApplication *)application
        {
            [[BHModuleManager sharedManager] triggerEvent:BHMDidEnterBackgroundEvent];
        }
        
        - (void)applicationWillEnterForeground:(UIApplication *)application
        {
            [[BHModuleManager sharedManager] triggerEvent:BHMWillEnterForegroundEvent];
        }
        
        - (void)applicationDidBecomeActive:(UIApplication *)application
        {
            [[BHModuleManager sharedManager] triggerEvent:BHMDidBecomeActiveEvent];
        }
        
        - (void)applicationWillTerminate:(UIApplication *)application
        {
            [[BHModuleManager sharedManager] triggerEvent:BHMWillTerminateEvent];
        }
        
        - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
        {
            [[BeeHive shareInstance].context.openURLItem setOpenURL:url];
            [[BeeHive shareInstance].context.openURLItem setSourceApplication:sourceApplication];
            [[BeeHive shareInstance].context.openURLItem setAnnotation:annotation];
            [[BHModuleManager sharedManager] triggerEvent:BHMOpenURLEvent];
            return YES;
        }
        
        #if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400
        - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
        {
            [[BeeHive shareInstance].context.openURLItem setOpenURL:url];
            [[BeeHive shareInstance].context.openURLItem setOptions:options];
            [[BHModuleManager sharedManager] triggerEvent:BHMOpenURLEvent];
            return YES;
        }
        #endif
        
        - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
        {
            [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveMemoryWarningEvent];
        }
        
        - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
        {
            [[BeeHive shareInstance].context.notificationsItem setNotificationsError:error];
            [[BHModuleManager sharedManager] triggerEvent:BHMDidFailToRegisterForRemoteNotificationsEvent];
        }
        
        - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
        {
            [[BeeHive shareInstance].context.notificationsItem setDeviceToken: deviceToken];
            [[BHModuleManager sharedManager] triggerEvent:BHMDidRegisterForRemoteNotificationsEvent];
        }
        
        - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
        {
            [[BeeHive shareInstance].context.notificationsItem setUserInfo: userInfo];
            [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveRemoteNotificationEvent];
        }
        
        - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
        {
            [[BeeHive shareInstance].context.notificationsItem setUserInfo: userInfo];
            [[BeeHive shareInstance].context.notificationsItem setNotificationResultHander: completionHandler];
            [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveRemoteNotificationEvent];
        }
        
        - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
        {
            [[BeeHive shareInstance].context.notificationsItem setLocalNotification: notification];
            [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveLocalNotificationEvent];
        }
        
        #if __IPHONE_OS_VERSION_MAX_ALLOWED > 80000
        - (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity
        {
            if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
                [[BeeHive shareInstance].context.userActivityItem setUserActivity: userActivity];
                [[BHModuleManager sharedManager] triggerEvent:BHMDidUpdateUserActivityEvent];
            }
        }
        
        - (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error
        {
            if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
                [[BeeHive shareInstance].context.userActivityItem setUserActivityType: userActivityType];
                [[BeeHive shareInstance].context.userActivityItem setUserActivityError: error];
                [[BHModuleManager sharedManager] triggerEvent:BHMDidFailToContinueUserActivityEvent];
            }
        }
        
        - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
        {
            if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
                [[BeeHive shareInstance].context.userActivityItem setUserActivity: userActivity];
                [[BeeHive shareInstance].context.userActivityItem setRestorationHandler: restorationHandler];
                [[BHModuleManager sharedManager] triggerEvent:BHMContinueUserActivityEvent];
            }
            return YES;
        }
        
        - (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType
        {
            if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
                [[BeeHive shareInstance].context.userActivityItem setUserActivityType: userActivityType];
                [[BHModuleManager sharedManager] triggerEvent:BHMWillContinueUserActivityEvent];
            }
            return YES;
        }
        #endif
        #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
        - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
            [[BeeHive shareInstance].context.notificationsItem setNotification: notification];
            [[BeeHive shareInstance].context.notificationsItem setNotificationPresentationOptionsHandler: completionHandler];
            [[BeeHive shareInstance].context.notificationsItem setCenter:center];
            [[BHModuleManager sharedManager] triggerEvent:BHMWillPresentNotificationEvent];
        };
        
        - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
            [[BeeHive shareInstance].context.notificationsItem setNotificationResponse: response];
            [[BeeHive shareInstance].context.notificationsItem setNotificationCompletionHandler:completionHandler];
            [[BeeHive shareInstance].context.notificationsItem setCenter:center];
            [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveNotificationResponseEvent];
        };
        #endif
        
      • 系统事件参数都存在BHContext的公开属性列表
  • 1.2 通用应用级事件:
    在系统事件的基础之上,扩展了应用的通用事件,例如modSetupmodInitmodSplash,可以用于编码实现各插件模块的设置与初始化
    • 扩展的通用事件如下: 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • 1.3 业务自定义事件:
    • 如果系统事件通用事件仍不满足应用实现需要,我们可以增加业务自定义事件
    • 我们可以通过继承 BHAppdelegate来扩展自己的事件,在我们自己的Appdelegate注册更多的事件。(关于模块的注册,我们在后面再进一步深入了解)
    • 自定义的事件的type值就是BHMDidCustomEvent >= 1000
    • BHModuleManager内有一个API专门处理业务自定义事件
      - (void)registerCustomEvent:(NSInteger)eventType
         withModuleInstance:(id)moduleInstance
             andSelectorStr:(NSString *)selectorStr {
          if (eventType < 1000) {
              return;
          }
          [self registerEvent:eventType withModuleInstance:moduleInstance andSelectorStr:selectorStr];
      }
      
  • BHModuleManager中有2个特殊事件:
    • BHMInitEvent事件: 初始化Module模块的事件,关键代码
      - (void)handleModulesInitEventForTarget:(id<BHModuleProtocol>)target
      withCustomParam:(NSDictionary *)customParam {
          BHContext *context = [BHContext shareInstance].copy;】
          context.customParam = customParam;
          context.customEvent = BHMInitEvent;
      
          NSArray<id<BHModuleProtocol>> *moduleInstances;
          if (target) {
              moduleInstances = @[target];
          } else {
              moduleInstances = [self.BHModulesByEvent objectForKey:@(BHMInitEvent)];
          }
      
      
      
          [moduleInstances enumerateObjectsUsingBlock:^(id<BHModuleProtocol> moduleInstance, NSUInteger idx, BOOL * _Nonnull stop) {
                  __weak typeof(&*self) wself = self;
                  void ( ^ bk )(void);
      
                  bk = ^(){
                      __strong typeof(&*self) sself = wself;
                      if (sself) {
                          if ([moduleInstance respondsToSelector:@selector(modInit:)]) {
                              [moduleInstance modInit:context];
                          }
                      }
                  };
      
                  [[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- modInit:", [moduleInstance class]]];
      
                  if ([moduleInstance respondsToSelector:@selector(async)]) {
                      BOOL async = [moduleInstance async];
      
                      if (async) {
                          dispatch_async(dispatch_get_main_queue(),^{
                              bk();
                          });
                      } else {
                          bk();
                      } 
                  } else { 
                      bk();
                  } 
          }];
      }
      
      • 遍历BHModules数组
      • 依次对每个Module实例调用modInit:方法;
      • 这里会有异步加载的问题:
      • 如果moduleInstance重写了async方法,那么就会根据这个方法返回的值来进行是否异步加载的判断
      • 默认不异步
      • modInit:方法里面干很多事情:
        • 比如说对环境的判断,根据环境的不同初始化不同的方法:
          -(void)modInit:(BHContext *)context {
              switch (context.env) {
                  case BHEnvironmentDev:
                      //....初始化开发环境
                      break;
                  case BHEnvironmentProd:
                      //....初始化生产环境
                  default:
                      break;
              }
          }
          
        • 再比如,注册一个业务服务初始化模块的某业务服务,给其传参:
          -(void)modInit:(BHContext *)context {
              NSLog(@"模块初始化中"); 
              NSLog(@"%@",context.moduleConfigName);
          
              // [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
              id<TradeServiceProtocol> service = [[BeeHive shareInstance] createService:@protocol(TradeServiceProtocol)];
              service.itemId = @"我是单例";
          
          }
          
        • ...
    • BHMTearDownEvent事件:卸载Module模块的事件,关键代码:
      - (void)handleModulesTearDownEventForTarget:(id<BHModuleProtocol>)target
          withCustomParam:(NSDictionary *)customParam {
      
              BHContext *context = [BHContext shareInstance].copy;
              context.customParam = customParam;
              context.customEvent = BHMTearDownEvent;
      
              NSArray<id<BHModuleProtocol>> *moduleInstances;
      
              if (target) {
                  moduleInstances = @[target];
              } else {
                  moduleInstances = [self.BHModulesByEvent objectForKey:@(BHMTearDownEvent)];
              }
      
              //Reverse Order to unload
              for (int i = (int)moduleInstances.count - 1; i >= 0; i--) {
                  id<BHModuleProtocol> moduleInstance = [moduleInstances objectAtIndex:i];
                  if (moduleInstance && [moduleInstance respondsToSelector:@selector(modTearDown:)]) {
                      [moduleInstance modTearDown:context];
                  }
              }
      }
      
    • 由于Module是有优先级Level,所以拆除的时候需要从低优先级开始拆
      • 即数组逆序循环
      • 对每个Module实例发送modTearDown:事件即可
  • BHServiceManager:BeeHive内部专门用于管理服务(注册反注册查询)的工具类
    • 核心API:
      #import <Foundation/Foundation.h>
      
      @class BHContext;
      
      @interface BHServiceManager : NSObject
      @property (nonatomic, assign) BOOL  enableException;
      
      + (instancetype)sharedManager;
      
      - (void)registerLocalServices;
      - (void)registerService:(Protocol *)service implClass:(Class)implClass;
      
      - (id)createService:(Protocol *)service;
      - (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName;
      - (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName shouldCache:(BOOL)shouldCache;
      
      
      
      
      - (id)getServiceInstanceFromServiceName:(NSString *)serviceName;
      - (void)removeServiceWithServiceName:(NSString *)serviceName; 
      
      @end
      
  • BHRouter: 是一个专门负责处理URL-HandlerBlock远程 APP事件的 插件
    • 对外公布的API和相应的介绍如下:
      // 协议头Key
      static NSString *const BHRURLSchemeGlobalKey = @"URLGlobalScheme";
      // 【路由表】事件分类:
      static NSString *const BHRURLHostCallService = @"call.service.beehive";//调用服务
      static NSString *const BHRURLHostRegister = @"register.beehive";//注册服务
      static NSString *const BHRURLHostJumpViewController = @"jump.vc.beehive";//跳转页面
      //【路由URL规则】:
      static NSString *const BHRURLSubPathSplitPattern = @".";//url路径分节点的标识
      static NSString *const BHRURLQueryParamsKey = @"params";//url路径传参Key
      static NSString *const BHRURLFragmentViewControlerEnterModePush = @"push";//页面打开的方式Push
      static NSString *const BHRURLFragmentViewControlerEnterModeModal = @"modal"; //页面打开的方式Model
      
      
      typedef void(^BHRPathComponentCustomHandler)(NSDictionary<NSString *, id> *params);//HandlerBlock的类型定义
      
      @interface BHRouter : NSObject
      
      - (instancetype)init NS_UNAVAILABLE;
      + (instancetype)new NS_UNAVAILABLE;
      
      + (instancetype)globalRouter;//模块管理工具默认协议头
      + (instancetype)routerForScheme:(NSString *)scheme;//新增一个协议头对应的路由表
      
      + (void)unRegisterRouterForScheme:(NSString *)scheme;//释放一个某协议头的路由表
      + (void)unRegisterAllRouters;//释放所有缓存好的路由表
      
      //handler is a custom module or service solve function
      - (void)addPathComponent:(NSString *)pathComponentKey
         forClass:(Class)mClass;
      - (void)addPathComponent:(NSString *)pathComponentKey
         forClass:(Class)mClass
          handler:(BHRPathComponentCustomHandler)handler;
      
      - (void)removePathComponent:(NSString *)pathComponentKey;
      
      //url - >  com.alibaba.beehive://call.service.beehive/pathComponentKey.protocolName.selector/...?params={}(value url encode)
      //url - >  com.alibaba.beehive://register.beehive/pathComponentKey.protocolName/...?params={}(value url encode)
      //url - >  com.alibaba.beehive://jump.vc.beehive/pathComponentKey.protocolName.push(modal)/...?params={}(value url encode)#push
      //params -> {pathComponentKey:{paramName:paramValue,...},...}
      //when call service, paramName = @1,@2,...(order of paramValue)
      
      + (BOOL)canOpenURL:(NSURL *)URL;
      
      + (BOOL)openURL:(NSURL *)URL;
      + (BOOL)openURL:(NSURL *)URL
       withParams:(NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)params;
      + (BOOL)openURL:(NSURL *)URL
       withParams:(NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)params
          andThen:(void(^)(NSString *pathComponentKey, id obj, id returnValue))then;
      
      @end
      
    • BHRouter 支持根据一个新的协议头,新增一个路由表 , 这是开发者 考虑到了 一套代码多套应用的场景!
      • 商家端买家端 一体的 电商
      • 内部员工版本To C版本 一体的 应用
      • To B版本 与 To C版本 一体的 应用
      • 互联网金融银行银行ToC端,有时候会根据不同的地级市也有不同的版本,多套应用一体,可随时切换
      • ...
    • BHRouter 与 蘑菇街 的 URL-HandlerBlock 不同的地方:
      • BeeHive把App内部事件和 跨应用事件 的 治理 分开了;
      • BHRouter专门负责 远程事件跨应用事件;
  • BHContext:单例
    imp文件内部有两个字典,用于存储模块信息(modulesByName)和服务信息(servicesByName) 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
    • 公布一些存储环境变量的属性和和对服务管理三个API如下:
      #import <Foundation/Foundation.h>
      #import "BHServiceProtocol.h"
      #import "BHConfig.h"
      #import "BHAppDelegate.h"
      
      typedef enum{  
      BHEnvironmentDev = 0,
      BHEnvironmentTest,
      BHEnvironmentStage,
      BHEnvironmentProd
      }BHEnvironmentType;
      
      @interface BHContext : NSObject <NSCopying>
      
      //global env
      @property(nonatomic, assign) BHEnvironmentType env;
      //global config
      @property(nonatomic, strong) BHConfig *config;
      //application appkey
      @property(nonatomic, strong) NSString *appkey;
      //customEvent>=1000
      @property(nonatomic, assign) NSInteger customEvent;
      
      @property(nonatomic, strong) UIApplication *application;
      @property(nonatomic, strong) NSDictionary *launchOptions;
      @property(nonatomic, strong) NSString *moduleConfigName;
      @property(nonatomic, strong) NSString *serviceConfigName;
      
      
      //3D-Touch model
      #if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400
      @property (nonatomic, strong) BHShortcutItem *touchShortcutItem;
      #endif
      
      //OpenURL model
      @property (nonatomic, strong) BHOpenURLItem *openURLItem;
      //Notifications Remote or Local
      @property (nonatomic, strong) BHNotificationsItem *notificationsItem;
      //user Activity Model
      @property (nonatomic, strong) BHUserActivityItem *userActivityItem;
      //watch Model
      @property (nonatomic, strong) BHWatchItem *watchItem;
      //custom param
      @property (nonatomic, copy) NSDictionary *customParam;
      
      + (instancetype)shareInstance;
      
      - (void)addServiceWithImplInstance:(id)implInstance serviceName:(NSString *)serviceName;
      - (void)removeServiceWithServiceName:(NSString *)serviceName;
      - (id)getServiceInstanceFromServiceName:(NSString *)serviceName;
      
      @end
      
    • 应用启动的时候,可以初始化一些上下文信息:
      - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
      
      [BHContext shareInstance].application = application;
      [BHContext shareInstance].launchOptions = launchOptions;
      [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可选,默认为BeeHive.bundle/BeeHive.plist
      [BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";
      
      [BeeHive shareInstance].enableException = YES;
      [[BeeHive shareInstance] setContext:[BHContext shareInstance]];
      
      [[BHTimeProfiler sharedTimeProfiler] recordEventTime:@"BeeHive::super start launch"];
      
      [super application:application didFinishLaunchingWithOptions:launchOptions];
      
      ...
      
      return YES;
      }
      
  • BHConfig:单例
    保存了一个config的NSMutableDictionary字典
    字典职责是维护一些动态的环境变量,作为BHContext的补充存在;API:
    #import <Foundation/Foundation.h>
    
    @interface BHConfig : NSObject
    
    + (instancetype)shareInstance;
    + (id)get:(NSString *)key;
    + (BOOL)has:(NSString *)key;
    + (void)add:(NSDictionary *)parameters;
    
    + (NSMutableDictionary *)getAll;
    + (NSString *)stringValue:(NSString *)key;
    + (NSDictionary *)dictionaryValue:(NSString *)key;
    
    + (NSInteger)integerValue:(NSString *)key;
    + (float)floatValue:(NSString *)key;
    + (BOOL)boolValue:(NSString *)key;
    + (NSArray *)arrayValue:(NSString *)key;
    
    + (void)set:(NSString *)key value:(id)value;
    + (void)set:(NSString *)key boolValue:(BOOL)value;
    + (void)set:(NSString *)key integerValue:(NSInteger)value;
    
    + (void)clear;
    @end
    
  • BeeHive :框架作者用这个类来公布框架所有的插件API
    • BeeHive里面也有一个triggerCustomEvent:方法就是用来处理这些事件的,尤其是处理自定义事件的:
      + (void)triggerCustomEvent:(NSInteger)eventType {
      if(eventType < 1000) { 
      return; 
      } 
      
      [[BHModuleManager sharedManager] triggerEvent:eventType];
      }
      
    • BeeHive公开的API:
      #import <Foundation/Foundation.h>
      #import "BHModuleProtocol.h"
      #import "BHContext.h"
      #import "BHAppDelegate.h"
      #import "BHModuleManager.h"
      #import "BHServiceManager.h"
      
      @interface BeeHive : NSObject
      //save application global context
      @property(nonatomic, strong) BHContext *context;
      @property (nonatomic, assign) BOOL enableException;
      
      + (instancetype)shareInstance;
      + (void)registerDynamicModule:(Class) moduleClass;
      
      - (id)createService:(Protocol *)proto;
      //Registration is recommended to use a static way
      - (void)registerService:(Protocol *)proto service:(Class) serviceClass;
      
      + (void)triggerCustomEvent:(NSInteger)eventType;
      @end
      
    • BeeHive设置 关联着全局环境参数 的上下文对象的时候,会默认加载本地服务列表中所有的服务和模块 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
  • BeeHive.bundle
    bundle内部存储两个plist:
    • BeeHive.plist : 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
    • moduleClasses 存储的是 一组模块 信息,每个模块信息都用一个字段存储。字段注解:
      • moduleClass : 模块类名
      • moduleLevel : 模块事件等级(Integer值,值越大。模块分层的辅助值)
      • modulePriority: 模块事件优先级(Integer值,值越大。同级模块下,分事件的优先级的辅助值) 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
    • BHService.plist 02-iOS架构设计 | iOS模块化开发 【模块的三级分类、模块划分策略、几种模块化处理方案、模块与模块管理设计】
      • service : 提供的服务暴露的接口文件(协议名)
      • impl : 服务的具体实现类类名
  • BHTimeProfiler:
    BHTimeProfiler就是用来进行计算时间性能方面的Profiler
  • BHWatchDog:
    可以开一个线程,设置好handler,每隔一段时间就执行一个handler
3.3) 优点:
  • MGJRouterCTMediatorBeeHive 三者间,属于最优秀的框架
  • 在模块化开发中,用Context管理环境变量,全局信息共享的方案优于用一个Constant文件管理
  • 基于接口Service访问,优点是可以编译时检查发现接口的变更,从而及时修正接口问题
    • 对模块管理进行归类管理,提高了事件的有条理性、可读性、可维护性
      • 模块管理分Level层级
      • 同级 模块 分 优先级
      • 通过 静态文件 plist 管理,增加了可维护性
      • 借助中间件,通过event的形式分发事件消息,模块管理的接口规则且统一
  • 基于接口URL Route访问
    • 支持 增加 映射表,为业务模块进一步分级提供了可能性
    • 支持 增加 映射表,为单进程多应用开发提供了便捷性
    • URL Route 内部 实现,可访问 模块服务
    • URL Route 实现,也用了Target-Action
  • 模块注册模式有两种
    • 静态注册,更倾向于 应用开发 时使用,为应用维护提供便利性
    • 动态注册,更倾向于 SDK 开发 时使用,为代码封装提供便利性
  • 接口简洁,易上手
  • ...
3.4) 缺点:
  • 基于接口Service访问,缺点是需要依赖接口定义的头文件,通过模块增加得越多,维护接口定义也有一定工作量
  • BeeHive模块管理功能全面,适合有一定体量的大应用项目。对于小项目还是太重了,可以进一步拆分子库
    • 针对SDK开发划分
    • 针对小项目拆分
    • 针对大项目,选取框架其部分功能,作为替代方案拆分
  • 针对不同模块的分级管理不够明确,可以进一步划分管理
  • ...

四、模块与模块管理

至此,关于模块与模块管理功能。我们结合前面的分析与介绍,我们可以做一下小结:

1. 应用项目、SDK等的模块划分

针对大项目:

  • 进行模块三级分类: 三级模块+组件+插件``应用级模块业务级模块功能级模块
    • 应用级模块 = 业务级模块1 + 业务级模块2 + 业务级模块3......
    • 业务级模块 = 若干功能级模块 + 若干UI组件
  • 模块内部可以进一步划分组件:
    • 功能级模块 = 功能组件A+功能组件B+功能组件C
    • ...

针对小项目:

  • 在小项目中【模块与组件】的概念往往混合使用,不怎么区分
  • 模块划分的颗粒度不用太细致,合适即可; 参考:二级模块+组件+插件
    • 如: 应用级模块业务级模块功能组件UI组件第三方社会化分享插件...

针对SDK:

在SDK/功能模块Pod库内部划分往往是按一级模块/二级模块划分处理的:

  1. 功能模块 = 若干功能插件+若干功能组件+若干UI组件
  2. 业务服务模块 = 若干功能模块+若干业务UI组件

2.模块的集成依赖:

  • 壳工程集成应用级模块
  • 应用级模块 依赖 业务级模块
  • 业务级模块 依赖 功能模块组
  • 功能模块 依赖 功能组件 + UI组件

3.模块的解耦开发与模块间的通讯管理:

  • 事件分类:
    • 系统事件
    • 应用业务事件
    • 自定义事件
  • 由模块管理者中间件作为模块间消息通讯的枢纽
    模块间通讯的方式:
    • 1.URL+Router
      • 负责 跨应用 服务 的 模块通讯
      • 负责 Hybrid方案 的 本地H5 业务包 模块 的通讯(类似于微信的小程序)
      • 负责 Hybrid方案 的 远程Web 业务页面 模块 的通讯
      • ...
    • 2.基于接口的实现Service访问

4.划分模块管理工具小功能模块

(为了方便组合,使其在适应【小项目、大项目、SDK开发】时,导入的管理工具不那么重)

  • 通讯的方式划分为小模块
  • 事件管理划分
  • 注册方式划分
  • 增加模块的管理层级

5.基于 阿里的 BeeHive 进行二次开发

在做这个模块管理工具的时候,我选择 基于 阿里的 BeeHive 进行二次开发。原因如下:

  • 功能相对完善且稳定:
    阿里的模块管理工具已经历过若干个大项目的检验,功能相对完善且稳定
    • 天猫、淘宝、支付宝等若干
  • 降低学习成本:
    很多开发者在比较 探索 组件化开发的过程中,已经对BeeHive这个工具相对熟悉,
  • 框架的名字:
    一个恰切的名字,即是一个恰切的定义。
    对于框架的准确定义,是一个优秀的插件成功的重要因素
    • BeeHive这个名称把模块的拔插描述的恰到好处:
      • BeeHive灵感来源于蜂窝
      • 蜂窝是世界上高度模块化的工程结构,六边形的设计能带来无限扩张的可能
      • 所以作者用了BeeHive来做模块化开发管理工具的命名
    • 我本人很喜欢这个名字,且目前没想到更好的名称代替ta,我们不如直接借鉴 这个名称取名:BeeHivePlus
      • Plus在具备原本版本的功能职责之外,有进一步的要求:
      • 增加模块层级管理功能
        • 应用级
        • 业务级
        • 功能级
      • 增加子模块库划分,使其更灵巧应对「大项目」、「小项目」、「SDK开发」、「平台套件开发」
        • 事件管理划分
        • 注册方式划分
    • Capability
      • 这个属于模块管理工具,是移动开发套件的一个模块化开发管理能力,我认为属于总体属于一个Capability
      • 而模块库划分的子库可以当做若干小组件/小插件
        • Plugin: 若是直接采用 第三方开源库BeeHive,我们可以用Plugin
        • Component: 我们此次决定借鉴阿里的BeeHive开发一个Swift版本的BeeHivePlus,所以这些应该划分为小功能组件
    • 使用Swift开发
      • Swift至今已经若干年了,ABI早已稳定,所以这次我决定使用Swift开发
      • 相信用Swift开发的BeeHivePlus在代码上,一定会比BeeHive更简洁,更优雅
    • 综上,总结一下命名:
      • 完整功能的框架:
        mpe.capability.BeeHivePlus
        • 解释一下: mpe = Mobile Paas Engineering,“移动开发套件PaaS服务工程”
          • mpaas:= Mobile Paas,代表着移动开发套件PaaS服务。我们自己创造一个框架是可以以为公司搭载MobilePaaS移动开发套件赋能公司和公司业务为愿景的
          • Engineering: 工程
        • capability: 模块管理工具,本身也是一个功能模块
        • BeeHivePlus = 基于BeeHive能力基础上提出进一步的要求
      • 划分的子库以及相关的职责备注:
        • 模块通讯1: mpe.component.urlRouter
          跨应用服务消息传递、H5页面Web服务(小程序)
        • 模块通讯2: mpe.component.moduleManager
          业务自定义事件
        • 服务管理模块: mpe.capability.serviceManager
          管理服务(注册反注册查询)
        • 消息管理: mpe.component.modEventMessageManager
          系统级事件+应用级事件
        • 环境与上下文配置: mpe.component.modEnvironment
          AppDelegate+Context+Config
        • 模块分层与模块分层管理分类: mpe.component.modModifier
          • 应用场景: SuperApp、NormalApp、SDK
          • 静态注册: BeeHive.bundle
          • 动态注册: BH_EXPORT_MODULE
          • Module Level分级: Application、Feature、Capability
          • mpe.component.modModifier.superApp
            = 静态注册 + 三级Module Level分级 + 依赖( 模块通讯1 + 模块通讯2 + 服务管理模块 + 消息管理:
          • mpe.component.modModifier.normalApp
            = 静态注册 + 二级Module Level分级 + 依赖(模块通讯2 + 服务管理模块 + 按需 添加)
          • mpe.component.modModifier.sdk
            = 动态注册 + 一级Module Level分级 + 依赖(按需 添加)
        • 模块分层与模块分层管理分类: mpe.component.modCommon
          TimeProfiler+WatchDog+Common+Defines

总结

本文要点小结

  • 本篇文章以 支付宝 为案例,讲述了:模块的三级分类模块划分策略
  • 紧接着介绍了模块化开发的意义,引出介绍了几款经典的 模块化处理方案,并做了对比分析
  • 最后,针对分析中提及的不足,提出了优化方案

本文尚未展开的内容

  • 本篇文章尚未针对模块化方案设计展开开发实践以及相关demo的介绍和分享
  • 本文也尚未进一步讲述应用的分层设计组件化开发实战
  • 同时本文在介绍几款模块化处理方案的过程中,没有进一步针对其中原理进行深入 关于模块化方案开发实践应用的分层设计组件化开发等内容,将在下一篇文章中体现

相关阅读

  • CTMediator 作者的博文:iOS应用架构谈-组件化方案
  • MGJRouter 作者的博文:蘑菇街 App 的组件化之路
  • BeeHive 作者的阿里团队博文:BeeHive:一个优雅但还在完善中的解耦框架

推荐阅读

  • 移动研发平台mPaaS

Swift/OC混编 项目的 模块化实践

  • iOS混编 模块化、组件化、经验指北

使用BeeHive作为模块管理工具

  • 阿里系的手机天猫,使用模块管理工具BeeHive实践 模块化架构
    手机天猫解耦之路
  • 蜂鸟商家版,老项目实践 模块化、组件化 架构演进 案例
    蜂鸟商家版 iOS 组件化 / 模块化实践总结

模块平台化管理工具

这个案例 介绍的 模块化 管理方案,是 搭建平台工具管理模块库 的方案

  • 适用于 业务线广移动端应用多 作为 商业 的大公司
    如京东:京东iOS客户端组件管理实践
  • 适用于 提供PaaSSaaS 服务的 云服务公司
    如:阿里云平台提供的 移动研发平台mPaaS 即是这类

其它

  • 模块化日常:神奇的 pod repo push 失败
  • 模块化日常:CocoaPods 1.4.0 真好用(并不)
  • 模块化日常:CocoaPods 库资源引用问题
  • 模块化日常:重名类
  • 模块化日常:耗时的发布
  • 模块化日常:开源库与私有库重名
  • 模块化日常:库间互相依赖

专题系列文章

iOS架构设计

  • 1.iOS架构设计|综述
  • 2.iOS架构设计|iOS模块化开发 【模块的三级分类、模块划分策略、模块化方案、模块功能设计】
  • 3.iOS架构设计|依赖包管理工具Cocoapods常用实践:组件库(本地库、远程库(公有、私有))创建、模板工程、划分、资源管理、优化等
  • 4.iOS架构设计|iOS模块化开发实践、iOS组件化开发【组件管理、组件分类、组件划分、组件开发】(待输出)
  • 5.iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、.xcfameworks等】
  • 6.iOS架构设计|iOS开发-设计模式(待输出)
  • 7.iOS架构设计|架构设计与架构演进(待输出)

探索iOS底层原理

  • 探究iOS底层原理|综述
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!