动手点关注 干货不迷路 👇
前言介绍
在软件架构领域,框架的功能类似于基础设施服务,是为实现某个业界标准而形成的组件规范。简单理解,框架就是制定一套规范或者规则,开发同学在该规范或者规则下工作。本文通过剖析框架实体 ServiceKit/Adapter ,来窥探其底层结构和架构设计。
背景描述
随着抖音业务的发展,为保障整体工程演进和迭代计划的高效运行,体系化建设已加速提上日程,Codebase(可通称为产物)融合是其中项目之一。该项目主要为开发同学提供底层复用能力、增强研发团队效能,致力于帮助开发同学轻松高效地研发、管理代码。
研发效率:需要支持不同宏变量进行 lint ,有重复 lint ,单个组件很难区分项目控制二进制发版频率,二进制需要频繁更新,宏会导致很多混编二进制,影响编译效率,如果以单个文件作为编译缓存单元,宏隔离也会降低编译缓存命中率。 可扩展性:扩展性差,缺乏动态能力和插件能力,添加新功能和修改原有功能会导致类实现的代码急剧膨胀。 圈复杂度:宏隔离的代码分散,修改和重构成本高。 组件粒度:无法支持项目间差异业务独立成组件,背离高内聚、低耦合原则。
我们的目标愿景是要做一套符合抖音工程架构体系,具备高效、通用、便捷能力的框架规范,让开发同学在标准规则下进行编码工作。
架构设计
启蒙图纸
启蒙设计是着手做事之前的抽象意识,如下图,在多个产物的研发环境下,将共同代码能高效的复用,差异性代码优雅的隔离开。
为了帮助新同学快速入手架构框架,笔者在做此框架 Swift 建设的过程中,基于近段时间经历的几个项目经验,总结出了一套系统性的脑图,下面和大家分享下框架系统化的全景。
框架全景思维
设计思想
适配器模式
注册与发现
服务演进
微服务
简单了解微服务后,以服务角度来看,多个 Target 产物根据各业务模块可划分为多个 Adapter 服务,搭配绑定多个适配器协议,这样能达成一对多效果。
服务注册
服务注册:是将提供某个服务的模块信息注册到一个公共的组件上去。(如下示例代码更加容易理解)
//服务注册
ServiceKit.register(AModuleServer);
服务发现
服务发现:是指使用一个注册中心来记录分布式系统中的全部服务的信息,以便其他服务能够快速的找到这些已注册的服务;不管是服务新增和服务删减都能实现自动发现。(如下示例代码更加容易理解)
//服务发现
ServiceKit.get(AModuleServer);
进阶图纸
蓝色框:抖音 Target
黑色框:抖音极速版 Target aXXXDOUYINAdapter:是 XXXDOUYINAdapterImpl 的服务实例。 XXXDOUYINAdapterImpl:是订阅者,发布者是持有 XXXDOUYINAdapterImpl 实例 XXXDOUYINAdapter 的主类。 < >XXXDOUYINAdapter :面向协议编程,抽象 Protocol 接口,抽离各自差异性、公共性代码的接口。
关系图纸
工程视角
流程实战
接下来我们进行下流程性实战演练。
技术细节
编译插拔
常规思路下,注册会放到 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;
Register {
if (isABTest) {
return <ObjectABProtocol>ObjectA.new;
} else {
return <ObjectABProtocol>ObjectB.new;
}
}
循环引用
为了防止 subscriber 与 publisher 在 block 使用或者主类与适配器的关联情况下导致循环引用,适配器底层运用了 NSProxy 来实现。如以下的 case 无需关心内存不释放问题。
场景例一
@implementation DYAudioViewForDOUYIN
RegisterAdapters(DYFeedInteractionControllerPrivateProtocol,DYFeedContaineAudioAdapter) {
if (GET_AB_TEST_CASE(enableAutoPlay)) {
return nil;
} else {
return [[DYAudioViewForDOUYIN alloc] init];
}
}
- (void)stopAudio:(BOOL)immediate
{
[[self weakTarget] refresh:^{
[[self weakTarget] refresh];
[self stop];
}];
}
- (void)stop
{
//do something
....
}
@end
场景例二
@implementation DYFeedContainer
GetAdapters(DYFeedContaineAudioAdapter,DYFeedContaineVideoAdapter, DYFeedModuleConfig)
- (void)stopPlay
{
id <DYFeedContaineVideoAdapter> adapter = [self DYFeedContaineVideoAdapter];
[[self DYFeedContaineVideoAdapter] stopVideo:^{
[adapter refreshView];
}];
self.myBlock = ^(){
[adapter refreshView];
};
}
@end
绑定关联
强关联:将各适配器强绑定关联在主类上,这样能实现适配器的生命周期跟随主类自动释放,在使用适配器对象时让内存持续处于最优状态。 弱关联:将主类弱关联在适配器上,这样能实现在隔离出来的附属类中,通过 Key ( self = 适配器)拿到主类,达到反向通信的效果。
多语言适配
E.g.
class ModuleADouYinLiteAdapter: NSObject,SwiftAdapterProtocol {
class func lazyRegister() -> NSObjectProtocol {
return ModuleADouYinLiteAdapter.init()
}
}
便利脚手架
Objective - C 宏
接口均用宏来封装。
//服务注册
RegisterAdapters(ModuleDouYinLiteAdapter) {
return ModuleDouYinLiteAdapter.new;
}
//服务发现
GetAdapters(ModuleDouYinLiteAdapter)
Swift Protocol 扩展
Swift 环境下不能友好的使用宏封装,此时我们可以通过对 Protocol 进行扩展,以达到封装效果。
//服务注册
class func lazyRegister() -> NSObjectProtocol,ModuleDouYinLiteAdapterProtocol {
return ModuleDouYinLiteAdapter.init()
}
//服务发现
Protocol.getAdapter(self,ModuleDouYinLiteAdapterProtocol.self)
使用视角
OC编码
服务注册
服务发现
Swift编码
服务注册
前置抽象协议接口,懒注册,支持切面。 支持在各个 Adapter 实现层中获取 WeakTarget (主类)。
辅助工具
就如很多人都喜欢玩的网游地下城与勇士( DNF ),辅助工具“连发”(顾名思义,连续发动,可以联想到传统单发步枪与自动步枪的区别)不仅让玩家节省了不少的按键成本,而且在连招上增强了打击节奏感。同样的道理,我们推荐使用 Xcode 自定义模板工具编程,让使用者减少打出代码的时间成本,在开发中更加聚焦处理编码逻辑。
使用规范
为让开发同学更加规范使用,我们在代码静态检查阶段进行代码的拦截矫正,同时基于现状列一下几个 Badcase 。
场景例一
只进行了分支判断逻辑隔离,没做到代码隔离,这样会将判断逻辑带到主类,使让包大小增加。
// E.g. 错误示例
- (void)masterFunction {
if ([self DYFeedAModuleLiteAdapter]) {
// lite code
} else {
// douyin or other Target code
}
}
//--------------------------------------------------------------------------
//E.g.正确示例
- (void)masterFunction {
[[self DYFeedAModuleAdapter] runFunction];
}
//各自Target实现runFunction协议方法
//in douyin
- (void)runFunction {
// code
}
//in Lite
- (void)runFunction {
// code
}
场景例二
在同一个产物内,一个协议被多个类实现( Debug 环境编译阶段会通过断言进行第一次拦截)。
//服务发现
ServiceKit.get(AModuleServer);
0
场景例三
Adapter 方法在不同产品线下可能返回空值,如果想拿 Adapter 做 一些逻辑编码,需要提前判断是否为空。
//服务发现
ServiceKit.get(AModuleServer);
1
生态建设
目前为止,多产物适配器框架实体 Adapter 已经在抖音数个平台业务线中批量使用,大部分 OC 业务场景均已覆盖,而且 Swift 场景能力也已建设完毕,框架母体 ServiceKit 已接入 20 + 个 App 。
写在最后
稳扎稳打
对于核心框架,我们写出的也许只有一行代码,但是会有几百万行甚至上千万行代码会经过它,一定要慎重思考。
加入我们
我们是负责抖音客户端基础能力研发和新技术探索的团队。我们在工程/业务架构,研发工具,研发平台,编译系统等方向深耕,支撑业务快速迭代的同时,保证超大规模团队的研发效能和工程质量。在性能/稳定性/高可用等方面不断探索,努力为全球数亿用户提供最极致的基础体验。同时也在推进 Swift/SwiftUI/端智能/自动化等技术在复杂工程中的落地,为研发提供最前沿的开发体验。细节介绍可以参考:。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...