作者|郭宇鹏序言在软件架构领域,框架的作用类似于基础设施服务,是为了实现而形成的组件规范一个行业标准。简单理解,框架就是制定了一套规范或规则,开发者在规范或规则下工作。本文通过分析框架实体ServiceKit/Adapter来窥探其底层结构和架构设计。背景描述随着抖音业务的发展,为保证项目整体演进迭代计划的高效运行,系统建设已提上日程,Codebase(通称产品)集成是项目之一.本项目主要为开发者提供底层复用能力,提升研发团队效率,致力于帮助开发者轻松高效地开发和管理代码。在Codebase整合过程中,技术团队在各业??务线方向探索差异化;在演进过程中,业务线之间的耦合度越来越强,开发者迫切需要一种差异化代码隔离的解决方案。下图展示了抖音和抖音至尊版模块的区别。回顾痛点,在以往的开发中,开发者普遍采用宏隔离(isLite或isPad)来区分不同产品之间的差异,但这种方式严重破坏了整个抖音项目的架构。以下是几个维度的分析。研发效率:lint需要支持不同的宏变量。有重复的棉绒。单个组件很难区分项目来控制二进制发布频率。二进制文件需要经常更新。宏会造成很多二进制混入,影响编译效率。如果使用单个文件作为编译缓存单元,宏隔离也会降低编译缓存命中率。可扩展性:可扩展性差,缺乏动态能力和插件能力,增加新功能和修改原有功能会导致类实现的代码急剧膨胀。圈复杂度:宏隔离导致代码碎片化,修改重构成本高。组件粒度:不能支持项目间的不同业务由组件独立组成,背离了高内聚低耦合的原则。我们的目标和愿景是打造一套符合抖音工程架构体系,高效、通用、便捷的框架规范,让开发者可以在规范的规则下进行编码。架构设计启蒙蓝图启蒙设计是开始做事之前的抽象意识,如下图所示,在多个产品的开发环境中,可以高效复用公共代码,优雅隔离差异化代码。为了帮助新同学快速上手架构框架,笔者在构建Swift这个框架的过程中,根据近期经历的几个项目的经验总结出一套系统的脑图。让我与您分享框架系统化的全景。全景思维框架里面的内容很多,这里还是想提一下,说不定能在哪个阶段给你启发;建议从树的根节点开始,有选择地了解;如果想大概了解一下,Enter3层左右就可以了,如果想了解更多,请到叶子节点(为了不影响阅读体验,比较详细的节点都被截掉了)。基于上述框架系统化的思路,整章会先介绍一些设计思路,然后再深入到性能等相关技术细节。由于篇幅有限,我们将对我们认为比较重要的技术点进行简化说明。适配器模式的设计思维在设计模式中,适配器模式(adapterpattern)有时也被称为包装风格或包装。将类的接口转换为用户所期望的。适配通过将类自己的接口包装在现有类中,使因接口不兼容而无法协同工作的类能够协同工作。——百科Adapter模式开发同学不需要关心每个模块的复杂程度,业务逻辑,选择类对象还是实例对象,各自单元如何初始化等等,只需要根据自己的任务调度做在包装好的适配器上,类似万能充电器(90后同学时代的产物:>),不用关注电池是华为的还是OPPO的,即插即用。注册与发现服务注册-服务发现思想服务演进下面三张图简述了web服务时代从传统服务到微服务时代的历史(传统服务->并发服务->分布式微服务),有兴趣的可以了解更多,这里就不过多介绍了。微服务微服务是一种以业务功能为中心的服务设计理念。每个服务都有独立运行的业务功能,不受语言限制的API对外开放。应用程序由一个或多个微服务组成。——维基百科,微服务简单了解微服务后,从服务的角度来看,可以将多个Target产品按照每个业务模块划分为多个Adapter服务,与多个适配器协议结合,实现一对一-许多影响。.我们深入介绍了内部设计思路。在使用阶段,一个主类可以向多个适配器类发送消息;在注册过程中,一个适配器类可以绑定多个适配器协议,满足两种场景:一种是多个产品必须实现的接口,可以放在一个公共协议上,第二种是一个接口单个产品必须实现的是放在一个独立的协议上。公共协议+独立协议可以组合起来,由同一个带上下文的适配器类实现。说到微服务,我们就不得不了解接下来的两个概念,服务注册和发现。Serviceregistration服务注册:就是向一个公共组件注册提供某种服务的模块信息。(下面的示例代码更容易理解)//服务注册ServiceKit.register(AModuleServer);服务发现服务发现:是指使用一个注册中心记录分布式系统中所有服务的信息,以便其他服务可以快速找到这些已注册的服务;可以自动发现新的和删除的服务。(下面的示例代码更容易理解)//服务发现ServiceKit.get(AModuleServer);AdvancedBlueBox:抖音TargetBlackBox:抖音SpeedVersionTargetaXXXDOUYINAdapter:是XXXDOUYINAdapterImpl的服务实例。XXXDOUYINAdapterImpl:是订阅者,发布者是持有XXXDOUYINAdapterImpl实例的XXXDOUYINAdapter的主类。<>XXXDOUYINAdapter:面向协议编程,对Protocol接口进行抽象,分离出各自差异代码和通用代码的接口。上图进一步总结了整个项目背景(抖音和抖音至尊版的两套代码,有重复也有不同,如何继续分享重复的代码,将差异化的代码隔离到各自的Target中products,不再耦合),我们要做的过程(通过adapter模式进行任务调度,面向协议编程,将共享和差异化的代码抽象成接口形式,在每个Target中实现各自的协议Impl),到达结果(通过方便的脚手架和辅助工具,用户可以低成本学习和理解,操作简单)。关系图工程视角从抖音现有工程架构的角度理解设计。流程实战接下来我们进行一次流程化实战演练。代码实战中,订阅类在App内存中创建一个实例,订阅者的生命周期由所有关联的发布者决定,比如多个controller将埋点逻辑汇总到一个processor,或者比如一个父controller对应到多个子控制设备。技术细节了解了上面的设计图后,我们再简单分析一下内部的技术细节。在编译插件的一般思路下,注册会放在App启动阶段,但是这样做很容易拖慢App的启动速度。为了在不影响启动速度的情况下尽早注册,需要根据编译器特性实现:__attribute__((section("name"))),通过attribute命令,在运行时将其写在.data段中编译,然后运行的时候读出来。下图介绍了编译注解的简单过程。代码示例__attribute((used,section(_DY_SEGMENT","_DY_MSG_ASSOCIATE_SUBSCRIBER_SECTION)))static_dy_message_pair_DY_MSG_UNIQUE_VAR=\{\&_DY_MSG_ASSOCIATE_PROTOCOL_METHOD(INDEX),\&_DY_MSG_ASSOCIATE_LOGIC_METHOD,\};利用上述编译注解的能力,搭配协议反射,就能达到使用时,get协议进一步读取.data段中存储的内存地址进行加载。此功能也称为延迟加载。支持切面的核心思想如下(伪代码)。代码块模型在注册阶段暴露,可以在块中做类似AB的逻辑方面。isABTest=YES;注册{if(isABTest){return
