前言
先前一篇 文章 ,我们围绕 “
移动客户端架构设计
" 这个话题展开讨论的过程中,我们做了基本的综述,并且针对架构设计
的大话题简扼地划分为五个部分:
我们先简单回顾一下,在前面的综述中介绍 移动端模块化开发
之前我们介绍的部分知识
项目周期简述
一个项目从立项到开发流程基本可以扼要概括为:
- 在
模块划分
之后,我们需要做的工作就是模块选择
或模块设计
模块选择1
: 公司的现存开发套件的模块,基于已有模块的基础上进行适当改造迭代模块选择2
: 采购 厂商的SDK 服务,如前面提及的 mPaaS 蚂蚁金融云服务等模块选择3
: 去获取开源第三方模块,并基于此项目需求,进行适当改造模块选择4
: 重新设计开发
模块化开发:
- 【模块化开发】 指的是:
将一个
系统或应用程序划分
为多个功能模块
每个模块
具有独立的功能
和接口
,并且可以单独开发
、测试
和维护
- 【模块化开发】 的
目的
是为了提高代码复用性
、可维护性
和可扩展性
,以及降低开发
和测试的复杂度
和难度
。
组件化开发:
- 【组件化开发】 则是
在模块化开发的基础上
,将模块进一步划分为更小的组件
每个组件
都有自己的界面
和业务逻辑
,并且可以独立部署和升级
。- 【组件化开发】 的目的是为了
进一步提高
代码复用性和可维护性,同时还可以实现更多的代码并行开发
和更灵活的应用程序构建
和发布
。 模块化开发
和组件化开发
都是为了提高软件开发的效率和质量- 但
组件化开发
相对于模块化开发
更加细粒度和 灵活,可以更好地满足大型应用程序/软件系统的的需求(换言之,一个模块可以集成若干组件,以实现模块的功能) - 个人总结,从设计上来看
- 模块强调职责(内聚、分离)
- 组件强调复用
模块设计:
无论是组件开发还是模块开发,我们都需要对其进行一定的设计工作,尽量避免不必要地重复工作和回避一些开发风险。
我们可以通过StarUML这个建模工具进行软件的设计工作,StarUML支持:
类图
、时序图
、用例图
等十几种图形模式;- 可以通过我 这篇文章初步了解StarUML
- 可以通过 官网 进一步学习StarUML的使用
综述
本文介绍的内容适用于项目完成了需求确认
,软件分层设计
之后的阶段。
在完成了软件分层架构设计之后,需要将模块划分归类,进一步合理设计软件架构,并借助模块管理框架
,实现App
的模块化开发
以达到App的快速集成
项目落地的技术方案。
本文内容框架
模块分类:
列举市面上流行的不同类型几款App,对模块进行划分
- 按照【
业务领域
】划分;- 按照【
功能服务
】划分;- ......
模块划分
介绍模块划分的策略
模块分类
是划分的结果模块划分
是分类的策略了解几种模块化方案
- 列举几种模块化方案
- 分析不同方案的优劣
模块功能设计
- 参考的模块化能力
- 定制模块化能力
- 理论分析
- 设计模块功能
模块化开发实践
- 开发模块管理框架
- 开发模块,实践模块管理与集成
一、模块的三级分类
-
- 金融-支付宝案例:
1. 【第一级模块
】: 应用级模块
以【金融-
支付宝
】为例分析:
模块划分:
- 【
第一级模块
】: 应用级模块:- 首页
- 理财
- 生活
- 消息
- 我
2. 【第二级模块
】: 业务服务模块
以首页为例
- 首页模块
点击首页的更多
入口,进入应用中心
,我们可以看到若干业务功能模块,支付宝很好地为他们做了归类: - 【便民生活】:
- 业务模块:
充值中心
、生活缴费
、交管12123
...
- 业务模块:
- 【购物娱乐】:
- 业务模块:
饿了么
、彩票
、淘宝
...
- 业务模块:
- 【财富管理】:
- 业务模块:
花呗
、借呗
、基金
、股票
...
- 业务模块:
- 【教育公益】:
- 业务模块:
蚂蚁森林
、运动
...
- 业务模块:
3. 【第三级模块
】: 功能模块
以便民生活大类的充值中心为例
- 业务模块:
充值中心
业务1
: 话费充值业务2
: 流量充值- ...
我们经常使用支付宝,就不难发现, 几乎所有的 服务型 业务模块,都离不开一个功能,那就是 支付
此处 的 支付功能
就是 支付功能模块
支付宝的本质就是 移动支付工具
, 其它 商业性业务 都是 围绕其 即时、便捷的 电子支付功能
向广大用户 提供 生活中的各类服务的。
支付宝
只有一个功能模块
?
那如此说来,是不是支付宝就只有一个功能模块, 支付模块
呢?
答案必然是 否定
的!!!
支付宝实现其支付模块
的功能,最起码实现了以下几个功能:
朋友们,若你还分不太清各级模块, 你也可以试着找一些应用来进行模块划分,欢迎留言,我们共同探讨!
其他案例分析推荐应用:
-
- 电商: 淘宝
-
- 教育: 中公教育、六分钟英语
-
- 医疗: 微医
-
- 车联网: 广汽传祺、哈啰出行&&嘀嗒出行
-
- 通讯: 中国移动
-
- 工具: AudioTools
-
- 娱乐: 抖音、网易云音乐
-
- 物联网: 海尔家居、华为手环
-
- 生活: 美团、菜鸟裹裹
- ...
二、模块划分策略
1. 划分策略
我们从模块的三级分类案例中,可以总结得出结论:
- 模块ke分三大类:
应用级模块
、业务级模块
、功能级模块
应用级模块
:
个数取决于App本身的设计者,是采用由几大应用模块的丰富版 或是 单一应用模块的简约风格- 可 集成 若干
业务级模块
- 必须集成
用户模块
:使用 用户数据进行 业务交互 单应用模块App
示例: 打车服务平台:嘀嗒出行多应用模块App
示例: 打车服务平台:哈啰出行
- 可 集成 若干
业务级模块
:- 在
应用级模块
下,有若干个业务级模块
业务级模块
示例:业务级模块
业务模块即是 公司 商业活动的各种渠道- 用户的消费渠道(于企业主而言)
- 广告渠道(于企业主而言)
- 应用渠道(于用户而言)
- .....
- 其 实现, 至少依赖 一个
功能级模块
- 其 实现, 需依赖 一定的业务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 Libraries | low-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 |
LBS | LBS功能: capability.lbs |
Network | 网络功能: capability.network |
Persistence | 数据缓存功能: capability.persistence |
Analytics | 埋点分析功能: capability.analytics |
... | XXX功能: capability.xxx |
四级概念
关于组件的概念以及相关的划分管理规则,会在讲解组件的姊妹篇文章,进行深入阐述,本次不铺展开来介绍
Component | 组件命名举例: |
---|---|
Product carousels | component.productCarousels |
Size pickers | component.sizePickers |
Story cards | component.storyCards |
... | XXX组件: component.xxx |
4. 小结
我们学高数的时候,总能听到教授念叨那几句华罗庚的经典语录:
数缺形时少直观,形少数时难入微;
数形结合百般好
,隔离分家万事休
这句话的意思是, 直观的 图形映像 + 逻辑化的数学公式映像 相互结合 才能 更好地认识理解且相对记忆深刻。
4.1 架构分层图
所以,我们不妨在这里来搞一个 具象化 的 小结:
- 依然以
支付宝为例:
三级模块划分的细节,同学们可以回到section1回顾一下 - 支付宝架构分层图:
- 应用层:
业务模块
- 服务层:
业务服务功能模块
- 容器层:
框架服务功能模块
- 框架层:
Native App Framework
、H5 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 模块划分层级图:
- 应用结构分级结构:
- 超级App
- 业务模块1
- 业务组件1
- 【UI组件+功能模块】组合1
- 【通用UI组件+功能模块】
- 【UI组件+功能模块】组合2
- 【定制化UI组件+功能模块】
- 功能模块1
- 插件1
- 插件2
- ...
- 功能模块2
- ...
- 功能模块1
- 【定制化UI组件+功能模块】
- ...
- 【UI组件+功能模块】组合1
- 业务组件2
- ...
- 业务组件1
- 业务模块2
- ......
- 业务模块1
- 超级App
三、了解几种模块处理方案
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
- 看起来挺好,这样做简单明了,没有多余的东西,项目初期推荐这样快速开发,但到了项目越来越庞大,这种方式会有什么问题呢?
- 显而易见,每个模块都离不开其他模块
相互引用,相互依赖,耦合严重: - 模块之间这样严重耦合,对
测试
/编译
/开发效率
/后续扩展
都有一些坏处;
2. 模块间相互通讯的目的:
模块间相互通讯的目的,总结就以下几个:
- 获取模块实例
页面跳转
页面传值
- 使用模块的
业务服务
/功能服务
- 传递
响应者链
信号- 捕获时机,执行相关逻辑
- ...
3. 那如何解耦呢?
按软件工程的思路,我们一看就知道应该加一个中间层
为了解决模块相互依赖耦合严重,广大码友们做过的一些尝试:
加了中间层后,一定程度摆脱解耦后的几种依赖模式:
我们且称中间层
为 Mediator
,Mediator的职责
就是模块间引用/通讯的目的。我们不妨进一步抽象总结之为,在实现不同模块解耦的前提下,达到:
- 1、获取模块实例
- 2、在模块间"消息传递"
那么这里有几个问题,我们需要回答清楚:
Mediator
如何实现消息传递
?模块A
只跟Mediator
通信,如何获取模块B
的接口?- 模块间 的解耦 是通过
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) 模块调用逻辑图:
3.2) 优点:
- 使用
url-block
的方案的确可以解决模块间相互引用
,达到解耦 - 蘑菇街专门用后台来管理路由
url
,统一管理- 解决了
iOS
和Android
的平台差异性
- 解决了
3.3) 缺点:
- 需要有地方列出各个组件里有什么 URL 接口可供调用
- 蘑菇街做了个后台专门管理
- 每个组件
都需要初始化
,内存里需要维护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
的参数传递受到限制,只能传递常规的字符串参数,无法传递非常规参数,如UIImage
、NSData
等类型 - 没有区分
本地调用
和远程调用
的情况,尤其是远程调用,会因为url
参数受限,导致一些功能受限 - 组件本身依赖了
中间件
,且分散注册使的耦合较多 url硬编
- ...
4.2 Protocol-Class服务注册绑定模式
1.) 代表框架
针对方案一的问题,蘑菇街又提出了另一种组件化的方案:
- 就是通过
protocol
定义服务接口
- 组件通过
protocol
接口,来访问实现接口的模块定义的服务:- 具体实现就是把
protocol
和class
做一个映射 - 同时在内存中保存一张映射表
- 使用的时候,就通过
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
模块调用逻辑图:
设计思想和方案一类似,都是通过给组件加了一层wrapper
:
3.2) 优点:
- 解决了方案一中无法传递
非常规参数
的问题,使得组件间的调用更为方便 - 结合方案1+方案2,可以区分
本地调用
和远程调用
的情况 - 通过
Protocol
可以有效约束
组件通讯时的传参个数,参数类型等 - 用
plist映射表
维护,代替内存中维护映射表
,相对更方便管理 系统事件
接入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) 模块调用运行时时序图:
调用关系概述:
- 首先由ViewController.h.m发起调用请求给
CTMediator
CTMediator
通过runtime
去调用目标模块DemoModule
- 目标模块
DemoModule
根据参数创建自己的一个实例,并把这个实例返回给CTMediator
CTMediator
再把这个实例返回给ViewController.h.m- (此时ViewController.h.m不需要知道这个实例的具体类型,只需要知道是UIViewController的子类),然后由ViewController.h.m决定以什么样的方式去展示DemoModule。
进一步细化DemoModule
内部的调用逻辑(步骤4、5):
每个步骤的相关代码:
- 步骤1:
ViewController.m
发起调用请求给CTMediator
CTMediator
通过分类CTMediator+CTMediatorModuleAActions.m
暴露ModuleA
的APIUIViewController *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
传过来的Target
、Action
、params
发起实际调用。
这个调用关系是通过 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.m
+CTMediator+CTMediatorModuleAActions.h.m
共同组成了一个面向DemoModule
的跳转,并且它不会在代码上依赖DemoModule
,DemoModule
是否提供了相应的跳转功能,只体现在运行时是否能够正常跳转。- 至此,
CTMediator
这个中间层实现了完全的独立,其他模块不需要预先注册,CTMediator
也不需要知道其他模块的实现细节。 - 唯一的关联就是需要
CTMediator+CTMediatorModuleAActions.h.m
中写明正确的target
+action
和正确的参数
- 而且这些
action
和参数只依赖于Target_A.h.m
action
和参数的
正确性只会在运行时检查- 如果
target
或action
不存在,可以在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
,
- 每增加一个模块,就要补充一个
Mediator
内部存在一定的参数硬编
- 项目体量大的时候,具有一定的 维护性 难度
- 对开发效率有一定影响
- 并不友好,且写法欠优雅
- 命名规则并不美观且相对复杂:每个
Target
都得加Target_A
,每个Action
都得加Action_
- 调用
Mediator
API 的 返回值 是什么 无法不明确知晓,很不友好,还得编写一定的类型检查
代码 - ...
4.4 模块服务管理工具:模块服务注册+URL-HandlerBlock路由注册表方案
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
编写各个业务模块可以实现插件化编程
各业务模块之间没有任何依赖,core
与module
之间通过event
交互,实现了插件隔离。- 基于接口的实现
Service
访问方式(Java spring
框架实现)BHModuleManager
消息分类
+模块消息定义
+模块注册
+模块调用
BeeHive
模块管理工具把事件划分为三类进行管理:
系统级事件
、应用级事件
、业务自定义事件
系统事件
通常是Application
生命周期事件- 在
系统事件
的基础之上,扩展了应用的通用事件
例如modSetup
、modInit
等,可以用于编码实现各插件模块的设置与初始化 - 如果觉得
系统事件
、通用事件
不足以满足需要,可以通过继承BHAppdelegate
来扩展当前应用的业务自定义事件
(BHAppdelgate
内原本就封装了对系统事件、通用事件的处理)
模块注册
之后就可以 给 其它模块 进行调用- 可以通过给 模块管理中间件
BHModuleManager
发送 另一个模块
的消息调用另一个模块 - 也可以通过 [[BeeHive shareInstance] createService:@protocol(XXXServiceProtocol)] 直接获取一个服务
- 基于跨应用实现的
URL Route
模式(iPhone
App
之间的互访)BHRouter
类似于 蘑菇街 的URL-HandlerBlock
方案- 支持
App间
互访,也支持Hybrid开发时,通过锻炼访问web业务包
(类似于小程序技术)
- 基于接口的实现
2.1) 了解其框架设计
1、实现特性
- 插件化的模块开发运行框架
- 模块具体实现与接口调用分离
- 模块生命周期管理,扩展了应用的系统事件
2、设计原则
- 因为基于
Spring
的Service
理念,虽然可以使模块间的具体实现与接口解耦,但无法避免对接口类的依赖关系。 - 为什么不使用
invoke
以及动态链接库技术实现对接口实现的解耦,类似Apache
的DSO
的方式?- 主要是考虑学习成本难度
- 以及动态调用实现无法在编译检查阶段检测接口参数变更等问题
- 动态技术需要更高的编程门槛要求。
3、项目名来源
BeeHive
灵感来源于蜂窝- 蜂窝是世界上高度模块化的工程结构,六边形的设计能带来无限扩张的可能
- 所以作者用了
BeeHive
来做为这个项目的命名
4、模块生命周期的事件
BeeHive
会给每个模块提供生命周期事件,用于与BeeHive
宿主环境进行必要信息交互,感知模块生命周期的变化。
2.2)了解框架事件分层
事件分为三种类型:
- 系统事件
- 通用事件
- 业务自定义事件
1、系统事件
系统事件通常是Application
生命周期事件,例如DidBecomeActive
、WillEnterBackground
等。
系统事件基本工作流如下:
2、通用事件
在系统事件的基础之上,扩展了应用的通用事件,例如modSetup
、modInit
等,可以用于编码实现各插件模块的设置与初始化。
扩展的通用事件如下:
3、业务自定义事件
如果觉得系统事件、通用事件不足以满足需要,作者还将事件封装简化成BHAppdelgate
,你可以通过继承 BHAppdelegate
来扩展自己的事件。
2.3) 模块注册方式
模块注册的方式有静态注册与动态注册两种。
1、 静态注册
通过在BeeHive.plist
文件中注册符合BHModuleProtocol
协议模块类:
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
编写各个业务模块可以实现插件化编程,各业务模块之间没有任何依赖,core
与module
之间通过event
交互,实现了插件隔离。但有时候我们需要模块间的相互调用某些功能来协同完成功能。
框架提供了两种形式的接口访问形式:
- 基于接口的实现
Service
访问方式(Java spring
框架实现) - 基于跨应用实现的
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) 工作流图+架构图+核心类关系图
- 系统事件基本工作流:
- 扩展的通用事件+系统事件基本工作流:
- 基本架构图:
- BeeHive详细架构图:
- 核心类关系图:
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
通过调用triggerEvent
API来处理所有的消息事件传递:- (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
生命周期事件,例如DidBecomeActive
、WillEnterBackground
等 - 系统
事件参数
都被当作环境变量缓存了起来,所有模块共享
- 系统事件基本工作流如下:
- 系统事件消息传递源码:
- (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 通用应用级事件:
在系统事件的基础之上,扩展了应用的通用事件,例如modSetup
、modInit
、modSplash
,可以用于编码实现各插件模块的设置与初始化- 扩展的通用事件如下:
- 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
- 核心API:
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
专门负责远程事件
、跨应用事件
;
- 对外公布的API和相应的介绍如下:
BHContext
:单例
imp文件内部有两个字典,用于存储模块信息
(modulesByName
)和服务信息
(servicesByName
)- 公布一些
存储环境变量的属性
和和对服务管理三个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
设置 关联着全局环境参数 的上下文对象
的时候,会默认加载本地服务列表中所有的服务和模块
- 在
BeeHive.bundle
bundle内部存储两个plist:BeeHive.plist
:moduleClasses
存储的是一组模块
信息,每个模块信息都用一个字段存储。字段注解:moduleClass
: 模块类名moduleLevel
: 模块事件等级(Integer值,值越大。模块分层的辅助值)modulePriority
: 模块事件优先级(Integer值,值越大。同级模块下,分事件的优先级的辅助值)
BHService.plist
service
: 提供的服务暴露的接口文件(协议名)impl
: 服务的具体实现类类名
BHTimeProfiler
:
BHTimeProfiler
就是用来进行计算时间性能方面的ProfilerBHWatchDog
:
可以开一个线程,设置好handler,每隔一段时间就执行一个handler
3.3) 优点:
- 在
MGJRouter
、CTMediator
、BeeHive
三者间,属于最优秀的框架 - 在模块化开发中,用
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库内部划分往往是按一级模块
/二级模块
划分处理的:
功能模块
= 若干功能插件
+若干功能组件
+若干UI组件
业务服务模块
= 若干功能模块
+若干业务UI组件
2.模块的集成依赖:
- 壳工程集成应用级模块
- 应用级模块 依赖 业务级模块
- 业务级模块 依赖 功能模块组
- 功能模块 依赖 功能组件 + UI组件
3.模块的解耦开发与模块间的通讯管理:
- 事件分类:
- 系统事件
- 应用业务事件
- 自定义事件
- 由模块管理者中间件作为模块间消息通讯的枢纽
模块间通讯的方式:- 1.URL+Router
- 负责 跨应用 服务 的 模块通讯
- 负责 Hybrid方案 的 本地H5 业务包 模块 的通讯(类似于微信的小程序)
- 负责 Hybrid方案 的 远程Web 业务页面 模块 的通讯
- ...
- 2.基于接口的实现
Service
访问
- 1.URL+Router
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
- 模块通讯1:
- 完整功能的框架:
总结
本文要点小结
- 本篇文章以
支付宝
为案例,讲述了:模块的三级分类
、模块划分策略
- 紧接着介绍了模块化开发的意义,引出介绍了几款经典的
模块化处理方案
,并做了对比分析 - 最后,针对分析中提及的不足,提出了优化方案
本文尚未展开的内容
- 本篇文章尚未针对
模块化方案设计
展开开发实践
以及相关demo的介绍和分享 - 本文也尚未进一步讲述
应用的分层设计
和组件化开发实战
- 同时本文在介绍几款模块化处理方案的过程中,没有进一步针对其中原理进行深入
关于
模块化方案开发实践
、应用的分层设计
和组件化开发
等内容,将在下一篇文章中体现
相关阅读
CTMediator
作者的博文:iOS应用架构谈-组件化方案MGJRouter
作者的博文:蘑菇街 App 的组件化之路BeeHive
作者的阿里团队博文:BeeHive:一个优雅但还在完善中的解耦框架
推荐阅读
- 移动研发平台mPaaS
Swift/OC混编 项目的 模块化实践
- iOS混编 模块化、组件化、经验指北
使用BeeHive
作为模块管理工具
- 阿里系的
手机天猫
,使用模块管理工具BeeHive
实践 模块化架构
手机天猫解耦之路 蜂鸟商家版
,老项目实践 模块化、组件化架构演进 案例
蜂鸟商家版 iOS 组件化 / 模块化实践总结
模块平台化管理工具
这个案例 介绍的 模块化 管理方案,是 搭建平台工具管理模块库
的方案
- 适用于
业务线广
且移动端应用多
作为 商业 的大公司
如京东:京东iOS客户端组件管理实践 - 适用于 提供
PaaS
或SaaS
服务的 云服务公司
如:阿里云平台提供的 移动研发平台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底层原理|综述