企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

导语 | 企业微信 iOS 端作为代码超越800万行的大型项目,接入了腾讯会议、腾讯文档、企业邮箱等功用插件。要交融多个异构体系、支撑多个团队一起协作开发一个 APP 是极大的挑战。一起,敏捷膨胀的代码量和功用模块数量给企微团队带来了编译耗时大增、模块耦合严峻等负担。为了适应事务的高速开展,企微团队进行了组件化、插件集成才干建设作业。本文将进行详细介绍。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

问题与挑战

跟着企业微信事务的快速迭代,企业微信 iOS 客户端工程成长为一个超越 800 万行代码的大型项目。因为 B 端需求多样化,企业微信不可能完结全部 SaaS 功用,多强联合是未来竞赛方向上的必选项,企微团队需求的是一个航母级能够搭载其它事务的渠道型 APP。一起企业微信客户端内交融了腾讯会议、腾讯文档、企业邮箱等功用,要交融多个异构体系、支撑多个团队一起协作开发一个 APP 是极大的挑战。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

敏捷膨胀的代码量和功用模块数量带来了一些新的问题:开发编译速度慢,全量编译耗时约80分钟,更新代码编译耗时通常超越20分钟;Xcode 工程文件体积敏捷膨胀,呈现工程加载耗时长,修正工程文件卡顿,编写代码时代码提示、断点调试呼应慢等问题;模块之间耦合严峻,相互依靠联系杂乱,没有清晰的架构分层,导致修正组件内部功用影响其它组件功用的问题,增加了代码的保护难度和测验的作业难度。

面临事务开展带来的问题和挑战,原工程已经不能满意当时需求。在这个布景之下,去年企微团队发动了企微 iOS 工程专项改造作业,经过一年的努力,完结了企微部分模块组件化、会议/文档/邮箱插件接入、Bazel 工程迁移等作业。本篇将详细介绍模块组件化和插件接入的探究与实践,假设您对其他项目感兴趣,欢迎留言并继续重视本公众号。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

组件化探究与实践

2.1架构介绍

针对历史架构的缺陷,企微团队整理了内部事务模块、根底模块、公共模块之间的联系,还考虑了会议、文档、邮箱插件和企微渠道之间的联系,引入了组件办理中心来做组件解耦,提出了企业微信 iOS 架构框架,如下图所示:

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

架构分为四层,通用层、通用底层、UI框架层、功用模块,其间通用层、通用底层用 C++ 编写,首要完结网络、db、日志、线程模型等通用才干,以及通用的事务才干接口,能够做到跨 iOS、Android、Mac、Win、linux 5 渠道代码复用。各个渠道在通用底层的根底上完结各自的 UI,iOS UI 层用 OC 编写事务组件,组件办理中心 为组件供给生命周期办理、组件间通信、告诉办理等才干,插件能够复用各个组件供给的接口,集成到企微的事务中来。

2.2 组件化作业拆解

经过架构整理,总共整理出 70 多个组件,其间包括约 1.7 万个源码文件800 万行代码,面临如此巨大的工程,重构作业将会带来不少的开发、测验作业量。团队不可能一蹴而就,一次性完结整个工程的重构和解耦,需求有一套可行的计划来逐渐完结。

企微团队将组件化作业拆解为 4 个阶段:

第一阶段,根底才干建设:完结组件办理容器,为组件、插件供给生命周期办理、组件间通信、告诉监听等根底才干;第二阶段,物理目录拆分:依据前期规划的组件,为每个组件新建一个独立文件夹,将属于组件的代码归拢到一处,从物理上完结阻隔;第三阶段,剖析组件之间的依靠联系:依靠联系首要分为两类,组件外部依靠接口和对外露出的接口。经过整理依靠联系,企微团队能够清楚看到每个组件的耦合程度以及改造的难度和作业量,耦合越严峻的组件改造作业量和影响面越大,一起经过依靠联系还能准确认位到需求改动的代码方位;第四阶段,组件拆分:依据依靠剖析的结果施行组件化,封装组件对外露出接口,将组件间调用从直接引证方法改为接口调用方法。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

2.3 组件化根底才干建设

如下图所示,组件办理中心 ModuleManager 具有以下才干:

组件生命周期办理:组件需求在 ModuleManager 注册,并完结相应接口,完结组件初始化逻辑、组件生命周期办理逻辑;组件间通信:组件供给对外才干接口,并完结这些接口,组件间能够用通道相互调用;体系事情/运用事情告诉:体系事情(运用发动、前后台切换、后台运用刷新、收到 APNS等),运用事情(账号切换等)告诉机制。组件能够监听相应事情,在事情发生时履行自己的逻辑;隐私权限办理:例如手机体系相册权限、定位权限、通讯录权限请求及运用,组件假设需求运用设备隐私相关的权限,需求向组件办理中心请求,统一办理灵敏操作;多账号数据阻隔:多个账号切换时要确保不同账号的数据阻隔,由组件办理中心确保不同账号不会串数据。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

关于组件间通信计划的挑选,已经有不少老练的组件间通信计划,企微团队挑选了基于协议的服务注册计划。组件间通信模型如下图所示,每个组件对外露出一组 Protocol,然后在组件内部完结对应接口。假设组件A需求调用组件B的接口,首要经过 ModuleManager 拿到组件B的接口完结对象,然后就能够调用组件B的接口。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

以下代码示例展现了一个接口的界说、完结、调用的完整流程。

// 文件:WWKUtilityServiceProtocol.h
@protocol WWKUtilityServiceProtocol <NSObject, WWKServiceProtocol>
/// 获取 string 类型的 systemconfig
- (std::string)stringSystemConfigForKey:(NSString *)key;
@end
// 文件:WWKUtilityService.mm
@implementation WWKUtilityService
- (std::string)stringSystemConfigForKey:(NSString *)key {
 return "config";
}
@end
// 调用方
[WWKFindService(WWKUtilityServiceProtocol) stringSystemConfigForKey:@"key"];

2.4 组件目录拆分

完结组件办理中心后,为施行组件解耦,首要要将组件代码从物理途径上分离隔。依据之前架构的整理,企微团队将代码分为若干个组件,每个组件为一个独立文件夹,将代码移动到对应目录。挪动文件的物理途径会遇到头文件找不到的编译报错,企微团队编写了一个东西主动修正头文件途径来辅佐完结拆分作业。

2.5 组件依靠联系剖析

组件物理目录拆分之后,就要进行代码逻辑的解耦合,假设是一个新项目或小型项目能够直接封装接口。可是企微有很多历史代码要处理,需求一套可行的计划来获取修正列表、评价解耦每个组件的作业量、确认改造作业需求投入多少人力和时刻完结,并辅佐开发进行修正作业,经过剖析组件的依靠联系能够获取到组件代码逻辑解耦列表。

剖析组件的依靠联系,企微团队能够从组件内文件的依靠联系入手,依靠联系分为两种,第一种是组件露出给其它组件依靠的符号,第二种是组件依靠其它组件的符号,在探究剖析依靠联系计划时,咱们共想到三种计划,别离是:剖析头文件依靠、剖析链接日志、解析 AST,前两种计划简略易完结,可是得到的结果精度不行,不能满意企微团队的需求,终究企微团队挑选了解析AST计划,运用 Clang LibTooling 编写东西,经过解析 AST 来剖析依靠联系。下面展开讲讲三个计划的流程及优缺陷。

计划一:剖析头文件依靠

企微团队首要想到的计划是解析源码依靠的头文件,解析流程如下图所示。

首要,履行一次完整的编译,得到编译中心产品“.d文件”,它包括了编译一个文件所需的所有头文件;其次,解析“.d 文件”,得到源码文件直接依靠、直接依靠的所有头文件,这儿的解析比较简略,用脚本逐行读取就能够完结;终究,过滤组件内部头文件、体系 SDK 头文件,得到组件外部依靠的头文件列表,经过剖析头文件所属组件得到组件间的依靠联系。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

编译中心产品示意图:

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

.d 文件内容示意图:

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

该计划的优点是原理和完结方法比较简略,只需对编译产品进行简略的解析即可得到结果;缺陷是得到的数据粒度太粗,依靠联系只能准确到文件,不能准确到详细符号。关于改造作业有一定指导意义,能够得到一个模糊的联系图,细节还得人工筛选一次,不能满意企微团队的需求。

计划二:剖析链接日志

企微团队在开发过程中常常遇到“Undefined symbols”类型的链接报错:

Undefined symbols for architecture arm64:
&nbsp; "_OBJC_CLASS_$_XXX", referenced from:
&nbsp; &nbsp; &nbsp; objc-class-ref in XXX.o
ld: symbol(s) not found for architecture arm64

这个报错原因是链接过程中符号缺失,报错日志会把所有缺失的符号列出来,企微团队能够运用这个报错信息获得组件链接过程中依靠的符号,直接剖分出依靠信息。

举个例子,要剖析“组件A”对外依靠、被外部依靠的符号信息,能够按照以下过程完结:

首要,结构一个子工程。子工程仅包括“组件A”的代码,工程的产品是一个动态库,因为“组件A”依靠了其它组件的符号,可是其它组件没有参加编译链接,所以在链接时会报错,错误类型是“Undefined symbols”,用脚本解析日志能够得到“组件A”对外依靠的所有符号;然后,同理,将“组件A”源码从主工程中去掉,形成一个子工程,然后编译工程,链接时同样会报错“Undefined symbols”,用脚本解析报错日志能够得到“组件A”被外部依靠的所有符号。

该计划优点是粒度能准确到详细符号,完结也比较简略。经过结构特别的工程,解析链接报错日志就能得到结果。缺陷是计划不行通用,假设要解析整个工程组件间依靠联系,需求结构很多的子工程,且定论要编译、链接完结后才干得到,功率很低;一起该计划得到的定论粒度不行细,只能准确到符号,没有符号所属源码文件、行号列号等信息,不能满意需求。

终究计划:解析 AST。LibTooling 是 LLVM 东西链里的接口,它供给了强大的 AST 解析和控制才干,用于编写基于 Clang 才干的独立东西。企微团队能够基于它的 ASTMatcher 编写东西解析源码,得到函数界说、函数调用等信息,从中能够剖分出组件的依靠联系。

举个例子演示它的才干,假设企微团队有下面一段代码,想要提取出其间的函数调用ModelA *model = [[ModelA alloc] initWithStr:@”AAAAA”];

// 示例源码
@implementation Demo
- (void)viewDidLoad {
 [super viewDidLoad];
 ModelA *model = [[ModelA alloc] initWithStr:@"AAAAA"];
}
@end

用下面的 Matcher 语句就能够达到企微团队的目的。

// Matcher
objcMethodDecl(
 hasAncestor(
 objcImplementationDecl().bind("myClass")
 ),
 forEachDescendant(
 objcMessageExpr().bind("funcCaller")
 )
).bind("mySelector")

运用东西 clang-query 能够快速验证 matcher 是否符合预期,解析结果如下图所示:

clang-query -p /xxx/xxx/compile_commands.json /xxx/xxx/Demo.mm
> set bind-root false
> set print-matcher true
> enable output dump
> set traversal IgnoreUnlessSpelledInSource
> m objcMethodDecl(hasAncestor(objcImplementationDecl().bind("myClass")),forEachDescendant(objcMessageExpr().bind("funcCaller"))).bind("mySelector")

理解了 ASTMatcher 的运用方法,接下来就是编写东西完结解析作业。东西解析流程如下:首要,运用 ASTMatcher 编写 Matchers 从 AST 中匹配企微团队需求的节点,提取出每个文件的函数界说/调用、变量界说/调用、类界说/引证列表,列表中还包括每个符号的代码文本,及所属文件途径,文件队伍号等信息;然后,比对符号运用文件与符号界说文件所属组件,能够区分是外部依靠符号还是内部符号,然后剖分出文件之间的依靠联系,终究汇总成组件间的依靠信息。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

终究每个组件会生成两个表格,对外露出符号和外部依靠符号,如下图所示,表格中包括符号界说的文件途径、行号、列号,运用符号的文件途径、行号、列号,以及符号的界说代码、运用符号的代码等信息。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

6)组件拆分

完结了组件依靠联系剖析之后就能够发动组件拆分作业了,组件拆分作业需求投入很多人力完结,开发搭档依据依靠联系输出的表格找到需求改造的代码方位,然后动手封装接口,修正接口调用方法,完结代码逻辑的解耦。

企微团队挑选了依靠相对简略的组件作为试点验证计划的可行性,在施行过程中不断完善计划,逐渐完结整个工程的组件化。在施行过程中企微团队发现有很大一部分接口属于胶水代码,封装作业简略重复,这类简略的接口能够用东西来生成代码,然后进一步减少人作业业量,这是后续的一个优化方向。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

插件集成

3.1 布景及计划

企微作为一个渠道型 APP ,要具有集成会议、文档、邮箱等多团队协作开发插件的才干,因为这些事务前期不是基于企微架构进行开发,有独立的架构和技能栈。

在组件化的根底上,企微团队为外部插件供给了集成的才干,将新插件看做一个组件集成到企微 APP 中,插件经过 ModuleManager 调用组件露出出一系列才干接口,插件也能够在 ModuleManager 注册接口,供其它组件调用。

插件开发涉及到多团队协作,不同开发团队有各自的代码库房、开发工程、规范流程等,怎么交融多个插件、让开发流程更顺利、高效的工作是一个不小的挑战。

传统的 SDK 开发形式如下图所示,SDK 开发搭档一般会写一个 Demo 工程来调试 SDK 功用,开发完结后由集成方接入 SDK,调用 SDK 供给的接口,在集成方工程联调接口。SDK 开发环境关于集成方是无感知的,不会依靠集成方的环境和数据。

这种方法在标准化 SDK 场景下是没有问题的。可是企微在集成会议、邮箱、文档插件时,插件侧要进行深度的事务交融和定制化开发,插件开发搭档需求运用企微的账号体系、数据进行调试,很难结构一个 Demo 工程模拟联调环境。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

针对这种特别的合作布景,企微团队提出了一种新的开发形式,如下图所示,先将企微的核心才干打包为一个 SDK,集成到插件开发壳工程中,插件开发完结后打包成 SDK 集成到企微工程中。经过双向接入对方 SDK 的方法,完结了开发、联调环境的统一。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

3.2 插件开发壳工程

为了处理外部插件开发、联调功率问题,企微团队搭建了一个专门用于插件开发的壳工程,能够做到无企微代码发动企微 APP,具有大部分企微才干,运用真实的环境、数据进行联调。在这个壳工程的根底上就能够开发新的插件。它具有以下特色:不依靠企微代码;开发联调环境对齐企微主工程;工程轻量,编译速度快;跨团队协作开发功率高。

壳工程如下图所示,工程由插件源码、图片/文案等资源文件、WeComKit、动态库组成。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

3.3 WeComKit 介绍

WeComKit 是企微根底才干 SDK,它是插件开发壳工程的核心。它将企微首要才干打包成一个动态库,以 API 的方法露出接口供外部插件调用,插件经过 ModuleManager 能够调用企微组件的接口。

打包 WeComKit 动态库时遇到一个问题,主工程依靠了部分插件的符号,打包 WeComKit 时不会链接插件的符号,因此会报错 Undefined symbols,需求在链接时运用参数-undefined dynamic_lookup 开启符号动态查找,能够处理这个问题。

3.4 插件开发流程

插件开发流程如下图所示:首要,将主工程组件、组件办理中心、插件、对外才干接口、资源文件等打包为 WeComKit;其次,将 WeComKit、主工程资源文件、主工程依靠的三方动态库接入到壳工程中,在壳工程里开发插件功用;终究,插件开发完结后,将代码、头文件、资源文件打包为 PluginFramework,集成到主工程中。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

终究为了让流程主动跑起来,企微团队搭建了两条蓝盾流水线。它们别离用于打包 WeComKit 和 PluginFramework。值得一提的是,流水线定期履行更新主工程、壳工程里运用的 Framework。

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

企业微信零耦合集成腾讯会议和腾讯文档插件化架构实践

总结考虑

在组件化的过程中,企微团队发现了面临企微这种体量大、需求杂乱的工程,传统的 Xcode 工程显得有些无能为力。它有工程卡顿、配置难以保护、工程不行灵活、编译慢等问题。业界常用计划是运用 CocoaPods 来办理组件化工程,但它是针对 Swift 和 Objective-C 设计的,不支持跨渠道,无法满意需求,终究企微团队挑选了一条不同的路。假设您感兴趣,欢迎留言并继续重视本公众号,咱们将继续输出系列内容。

兔年在即

腾讯云开发者福利加码

独家限量红包封面来啦

重视腾讯云开发者公众号

后台回复888

参加福兔红包封面抽奖

你可能感兴趣的腾讯工程师著作

| 你的2022年度开发者关键词,请查收>>

| React语境下前端DDD的长年探究经验

| 国民级运用:微信是怎么避免崩溃的?

| 从Linux零拷贝深入了解Linux-I/O

技能盲盒:前端后端AI与算法运维工程师文明

阅览原文