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

本文为InfoQ中文站特供稿件,首发地址为:InfoQ BeeHive。如需转载,请与InfoQ中文站联系

1.为什么需要BeeHive?

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

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

develop
develop

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

arch
arch

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

arch
arch

面条式的耦合,导致上层业务受限于底层基础库的依赖影响,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架构如下:

architecture
architecture

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

module
module

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

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


�service
�service

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

Module遵循BHModuleProtocol后,能够捕获App状态的回调,并拥有App生命周期内的全局上下文,通过context可获取配置参数,模块资源以及服务资源。
以BeeHive作为底层框架的App,除了解耦带来的便利,开发人员在开发新App过程中涉及相同功能的Module,无需重复造轮子,直接移植Module,开发一个App如同拼装积木,能组合需要的功能业务。

3.2 Service

�service
�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也能让更多人受用。想象一下像蜜蜂一样优雅地搭建每个蜂窝模块。


�service
�service

5. 参考

  1. Spring相关资料
  2. Apache DSO参考链接
  3. Cocoapods资料
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容