写点什么

BeeHive,一次 iOS 模块化解耦实践

2017 年 2 月 22 日

去年 GMTC 大会天猫无线专家高嘉峻分享了天猫 iOS 是如何做解耦的,并提到了其中的模块化方案 BeeHive,后来他将其整理成文章,推荐阅读:

手机天猫解耦之路

在本文,天猫的戴鹏继续分享了BeeHive 的目的,举例说明最佳实践,并且剖析其结构和原理。

1. 为什么需要 BeeHive?

在天猫 App 的快速发展过程中,人员不断壮大,业务不断复杂,代码量随之增多,带来的是协作开发中遇到各种各样的问题。

你是否曾在这样的环境下艰难开发?畏手畏脚地边做需求边改 BUG。

同时 iOS 的工程代码的耦合可能是这样的:

AppDelegate 中包含大量库的 init 以及其他操作,少则几百行,多则上千行,无关代码堆积在其中,维护成本极高,不同库的调用逻辑互相交错,如下图所示:

面条式的耦合,导致上层业务受限于底层基础库的依赖影响,BUG 排查缓慢、新功能增加效率随代码量递增而不断递减。

1.1 开发中主要问题

开发过程中总结了以下 App 开发中遇到的问题:

  • 功能代码之间的依赖复杂,可维护性差
  • 协同开发过程中,并行开发存在 block 情况
  • 功能界限不清晰,基础功能模块变动,会导致上层业务受到影响
  • 各团队负责功能模块,在主工程中有耦合代码
  • 上层业务会出现反向提供功能给底层情况
  • 性能分析优化,随代码增加变得困难

1.2 App 和开发人员的诉求

一个 App 应该有如下特性:

  • 功能可维护性
  • 功能可用性
  • 功能具有良好性能
  • 功能可分析,可量化
  • 功能可单元测试

开发人员希望协同开发中能够做到以下几点:

  • 不希望被别人 block 住开发
  • 依赖库版本、约定的接口要稳定
  • 以最少侵入式代码来接入某个功能

代码隔离开发问题,通过 Cocoapods 得到解决,代码层面达到了分割,但逻辑功能上的耦合问题还是无法解决。开发人员希望在扩展业务的同时做到快速稳定,因此需要有一种 App 模块解耦方式来让开发人员中免受依赖关系的痛苦,于是让开发人员产生了打造一个 BeeHive 全局基础框架的想法。

2. BeeHive 的最佳实践

BeeHive 的使用方法可以参考 BeeHive 的 README 。这里举一个实际开发中的例子。

2.1 3D-Touch 例子

2.1.1 场景 1: 搭建 3DTouch 场景

iPhone 6s 及以上的设备支持 3D-Touch 后,几乎所有应用都在适配其特性,按照惯例,在 AppDelegate 中包含如下代码:

复制代码
-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
....
}

这意味着 AppDelegate 要增长代码行数,实则 QuickAction 的功能没有必要写在 AppDelegate 中。利用 BeeHive 框架特性,创建 3DTouch Pod,独立 3DTouch 相关业务功能。

复制代码
-(void)modQuickAction:(BHContext *)context
{
....
//process context.shortcutItem
}

2.1.2 场景 2:3DTouch 需要动态化,可量化

一个动态配置 quickAction 需求的到来,以往的做法需要引入配置 Module,创建对应一系列调用流程,这时只需要调用配置 Service 即可,而且希望更早的更新 quickActionItem,于是可以调用 modInit 来实现。

复制代码
-(void)modQuickAction:(BHContext *)context
{
....
//update config by configCenter Service
}

产品方还希望知道用户都用了哪些 QuickAction,这时调用 UserTrack Service 即可,诸如此类的一个上层业务,开发人员要调用 Log,Cache 等等服务,采用 BeeHive Service 形式后只需一行调用即可。

2.1.3 场景 3:3DTouch 需要做到个性化

在没有服务端的情况下,如何做到 QuickAction 个性化,注册并提供了 3DTouchBHService,给其他业务调用比如某个功能页面

复制代码
-(void)updateAccessTimesWithActionURL:(NSURL *)actionURL
{
....
// save view controller access times by cache service
// update local quickAction Items by access times and any other element
}

上面三个典型场景主要涉及的到 BeeHive 几大功能点:

  • Module 的创建,感知 App 生命周期
  • 对内引入、调用 Service
  • 对外提供 Service
  • 功能移植,无需 copy,podfile 中增加 pod 源

整个 3DTouch 开发过程中不涉及其他其他功能的具体实现,面向切片编程过程中,只要关心自己模块对应的需求即可。

3. BeeHive 结构与原理解析

BeeHive 借鉴了 Spring Service、Apache DSO 的架构理念,采用 AOP+ 扩展 App 生命周期 API 形式,将业务功能、基础功能模块以模块方式以解决大型应用中的复杂问题,并让模块之间以 Service 形式调用,将复杂问题切分,以 AOP 方式模块化服务,举例来说日志、埋点模块采用 AOP 方式后,业务方不需要考虑日志、埋点的相关代码,只要以 createService 去声明调用 Service 即可。

相应的 BeeHive 架构如下:

Core + plugin 的形式可以让一个应用主流程部分得到集中管理,不同模块以 plugin 形式存在,便于横向的扩展和移植。

图中的 BHContext,是 BeeHive 的配置文件,提供全局统一上下文信息。

图中的 BHCore 即 BeeHive 提供注册、创建 Module、Service 逻辑,Module、Service 注册和调用逻辑只和核心模块相关,Module 之间没有直接的关联关系。

BeeHive 核心思想涉及两个部分:

  1. 各个模块间调用从直接调用对应模块,变成调用 Service 的形式,避免了直接依赖。
  2. App 生命周期的分发,将耦合在 AppDelegate 中逻辑拆分,每个模块以微应用的形式独立存在。

BeeHive 提供了三种不同的调用形式,静态 plist,动态注册,annotation。Module、Service 之间没有关联,每个业务模块可以单独实现 Module 或者 Service 的功能。

3.1 Module

(点击放大图像)

图中包含了主要的BeeHive 启动过程以及Module 的时序逻辑。Module 的事件分发源于BHAppDelegate 中的triggerEvent,对应GlobalContext 也在回调中提供给业务方。

BHAppDelegate 中除了回调系统的事件,还将 App 生命周期进行扩展,增加 ModuleSetup,ModuleInit,ModuleSplash,此外开发人员还可以自行扩展。

(点击放大图像)

扩展周期过程中,同时加入Module 分析量化功能,每个模块Init 的耗时均可计算出来,为性能优化做到数据上的支持。一个App 的业务增多过程中,通过分析定位Module 的Init 耗时可以确定需要优化的Module。

Module 遵循 BHModuleProtocol 后,能够捕获 App 状态的回调,并拥有 App 生命周期内的全局上下文,通过 context 可获取配置参数,模块资源以及服务资源。

以 BeeHive 作为底层框架的 App,除了解耦带来的便利,开发人员在开发新 App 过程中涉及相同功能的 Module,无需重复造轮子,直接移植 Module,开发一个 App 如同拼装积木,能组合需要的功能业务。

3.2 Service

(点击放大图像)

上述图中包含Service 相关的逻辑,业务A 可以通过createService 直接调用服务,Module 根据需求动态注册某个服务。Service 的调用和实现,核心是BHServiceManager。可以单独创建Services interface Pod,统一放置要用的Services,这样的业务依赖就从网状式变成中心式,业务方只需依赖Services 一个。

Service 可以动态共享对象,按需加载,BeeHive 逻辑是将基础服务注册在 plist 中,业务型服务允许 Service 不先注册,直到业务需要时才被动态注册。

Service 支持两种不同模式:

  • 单例: 对于全局统一且无状态服务,建议使用这种创建形式,这样有利于 Service 的统一管理以及减少不必要内存消耗。
  • 多实例: 每次调用服务都重新创建新的服务,对于涉及状态以及状态变化的服务最适合使用多实例方式。

在多线程环境下遇到了 Service 读写问题,已通过 Lock 来已避免 Array crash 问题。

不过 Service 还存在如下问题:

  • Service 依赖关系, 导致底层依赖的 Service 没有被创建时就被调用。
  • 规划 Service、Module 创建顺序,使得 App 达到秒开,优化性能体验。

前者依赖问题计划通过调度机制来解决,后者还需要将 AppDelegate 更多业务剥离以及实践才可,这里不细谈。

4. BeeHive 背后的思考

BeeHive 以一个分发 App 状态和统一 Service Interface 的架构形式解决了多团队多开发人员协同开发中的耦合问题。对于实践过程中的开发成本,适应需要一定过程,但逻辑理顺后,应用起来不成问题。就收益而言,BeeHive 更适合大型的多人项目以及快速移植的项目,小项目使用起来较复杂,有些得不偿失。

至此,BeeHive 中主体已分析到位,BeeHive 是一个正在成长的 iOS 框架,目前 Star 已 1500+,希望大家可以集思广益,多提 issue、Pull Request,这样 BeeHive 也能让更多人受用。想象一下像蜜蜂一样优雅地搭建每个蜂窝模块。

5. 参考

  1. Spring 相关资料
  2. Apache DSO 参考链接
  3. Cocoapods 资料

感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017 年 2 月 22 日 16:075321

评论

发布
暂无评论
发现更多内容

ECMAScript 6新特性简介

程序那些事

nodejs ES6 ECMAScript 6

两天,我把分布式事务搞完了

yes的练级攻略

分布式事务 seata

关于Java调用类的main方法

谷鱼

Java 包位置

架构师第1课作业及学习总结

小诗

Spring 5 中文解析数据存储篇-@Transactional使用

青年IT男

spring

常用设计模式

叶鹏

oeasy 教您玩转linux 010304 图形界面 xfce

o

坚持新媒体写作第21天了,聊聊我为什么喜欢写作

老胡爱分享

学习 写作 习惯养成 坚持 随笔杂谈 讨论写作

简述JVM垃圾回收

叶鹏

用户密码验证函数

叶鹏

食堂卡就餐卡系统

叶鹏

高难度对话读书笔记—情绪篇

wo是一棵草

18 张图,一文了解 8 种常见的数据结构

沉默王二

Java 数据结构

【性能优化】面试官:Java中的对象都是在堆上分配的吗?

冰河

面试 性能优化 JVM 性能调优 逃逸分析

架构师训练营12周作业

叶鹏

被我玩坏的git:除了之前的工作、当网盘用,还能这么玩

小Q

Java git 程序员 架构 开发

Springboot 定时任务

hepingfly

定时任务 springboot 注解

anyRTC云端录制功能上线

anyRTC开发者

WebRTC 语音 直播 RTC 安卓

架构师训练营第7周作业

叶鹏

Python 中 \x00 和空字符串的区别,以及在 Django 中的坑

AlwaysBeta

Python django 编程

架构师训练营第八周作业

叶鹏

小白理财先转变思维理念

boshi

理财 收入 财富自由

从零开始搭建完整的电影全栈系统(五)——WEB网站、Api以及爬虫的部署

刘强西

爬虫 网站搭建 部署与维护

实战中学习浏览器工作原理 — 排版与渲染

三钻

CSS 前端 浏览器

简述 CAP 原理

叶鹏

一文学懂递归和动态规划!

码农田小齐

算法 数据结构和算法

前端如何优雅处理类数组对象?

pingan8787

Java web前端

一个草根的日常杂碎(9月21日)

刘新吾

生活 现实纪录 随笔

微服务的框架(Dubbo)架构

叶鹏

Spring 5 中文解析数据存储篇-编程式事物管理

青年IT男

Spring5

架构师训练营第四周作业

叶鹏

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

BeeHive,一次iOS模块化解耦实践-InfoQ