本文已参与「新人创造礼」活动,一同开启创造之路。
组件间通讯,但凡大一点的项目都会做模块化开发,必然会遇到兄弟组件解耦、通讯问题。
那如何不互相依赖模块,又能够彼此传输音讯呢?网上的计划是有许多了,比方:
- URL 路由
- target-action
- protocol
iOS:组件化的三种通讯计划 这篇写的挺不错,没了解的同学能够看一下
也有许多第三方组件代表,MGJRouter
、CTMediator
、BeeHive
、ZIKRouter
等(排名不分前后[手动狗头])。
但他们或多或少都有各自的优缺点,这儿也不展开说,但根本上的有这么几种问题:
- 运用起来比较繁琐,需要了解本钱,开发起来也需要写许多冗余代码。
- 根本都需要先注册,再完成。那就无法确保代码必定存在完成,也无法确保完成是否跟注册出现不一致(当然你能够添加一些校验手法,比方静态检测之类的)。这一点在比较大型的项目里都是很痛的,要不就不敢删去前史代码来积债,要不便是莽曩昔,测验或者线上出现问题[手动狗头]。
- 假如存在 Model 需要传递,要不下沉到公共模块,要不便是转
NSDictionary
。仍是公共层积债或者模型改变导致运行时出问题。
那有没有银弹呢?这便是本次要讲的完成办法,换个角度处理问题。
与众不同的计划
经过上述的问题,想一下咱们想要的完成是什么样:
- 不需要添加开发本钱,也不需要了解全体的完成原理。
- 由组件供给方供给,先有完成再有界说,确保 API 是彻底可用的,假如完成产生改变,调用方会编译时报错(问题露出前置)。且其他模块不依赖但又能够准确调用到这个办法。
- 各类模型在模块内是正常运用的,且对外露出也是能够正常运用的,但又不用去下沉在公共模块。
是不是感觉要求很过火?就像一个渣男既不想跟你成婚,又想跟你生孩子[手动狗头] 。
但能不能完成呢,的确是能够的。但处理办法不在 iOS 自身,而在 codegen。铺垫到这儿,咱们来看看详细完成。
GDAPI 原理
在笔者所在的稿定,之前用的是 CTMediator
计划做组件间通讯,当然也就有上面的那些问题,甚至线上也出现过由于 Protocol
找不到 Mediator
导致的线上 crash。
为了处理界说和完成不匹配的问题,咱们期望界说必定要有完成,完成必定要跟界说一致。
那是否就能够换个思路,先有完成,再有界说,从完成生成界说。
这点参阅了 JAVA 的注解机制,咱们界说了一个宏 GDM_EXPORT_MODULE()
,用于阐明哪些办法是需要开发给其他模块运用的。
// XXLoginManager.h
/// 判别是否登陆
- (BOOL)isLogin GDM_EXPORT_MODULE();
这样在组件开发方就完成了 API 敞开,剩余的作业便是如何生成一个调用层代码。
调用层代码其实也便是 CTMediator
的翻版,经过 iOS 的运行时反射机制去寻觅完成类
// XXService.m
static id<GDXXXAPI> _mXXXService = nil;
+ (id<GDXXXAPI>)XXXService {
if (_mXXXService == nil) {
_mXXXService = [self implementorOfName:@"GDXXXManager"];
}
return _mXXXService;
}
咱们把这些生成的办法调用,生成到一个 GDAPI
模块统一存储,当然这个模块除了上述模块的 Service 层是要有详细的 .m
来做落地,其他都是 .h
的头文件。
那调用侧只需要 pod 添加依赖 s.dependency 'GDAPI/XXXXService'
即可调用到详细完成了
@import GDAPI;
...
bool isLogin = [GDAPI.XXService isLogin];
这儿肯定有同学会问,生成进程呢???
笔者是用 Ruby
代码完成了整个 codegen 进程,当时没挑选 Python
首要是为了跟 cocoapods
运用相同的开发语言,易于做侵入设计,但其实用其他语言都没问题,经过 shell
脚本做中转即可。
这儿源码有些定制化完成,放出来现在也是徒增我们烦恼,所以讲一下生成要害进程:
- 遍历组件所在目录,取出所有的
.h
文件,缓存在Map<文件途径,文件内容>
(一级缓存) - 解析存在
GDM_EXPORT_MODULE()
的办法,将办法的称号、参数、注释经过正则手法分解成相应的特点,存储到Map<模块名,API 模型列表>
(二级缓存) - 关于每一个 API 模型进行进一步解析,解析入参和出参,判别参数类型是否为自界说类型(模型、署理、枚举、包含杂乱的
NSArray<CustomModel *> *
等),假如有存在,则遍历一级缓存,找到自界说类型的界说,生成对应的 Model -> Procotol 等,且存储在多个 Map 中Map<类名/署理名/枚举名,详细解析后的模型>
(三级缓存)
有了上述各种模型,就差不多完成了 AST (抽象语法树) 的生成进程,至于为什么是用的正则而不是 iOS 的 AST 东西,首要原因是想做的很轻,尽量减少我们的构建时长,不要经过编译来完成。
- 有了 AST 生成就变得很简单,模版代码 + 模版输出即可
能够看到已经有大量模块生成了相应的 GDAPI
履行时长在 2S 左右,由于有一个预履行的进程,来做组件项目化,这个也算是特殊完成了。 实质上履行也就 1S 即可。
还有一点要说的是履行时机是在 pod install / update
之前,这个是经过 hooks cocoapods 的履行进程做到的。
一些难点
嵌套模型
上面尽管大略的讲了下 Model / Procotol 会生成 Protocol,但其实这一部分的确是最困难的,也是由于前史积债问题,下沉在公共模块的巨大的模型在各个组件里传输。
那要把它彻底的 API 化,就需要对它的特点进行递归解析,生成彻底符合的 protocol
例如:
... 举例为伪代码,OC 代码的确很烦琐
class A extends B {
C c;
NSArray<D> d;
}
/// 测验
- (void)test:(A *)a GDM_EXPORT_MODULE();
生成结果就如下图(伪代码):
@protocol GDAPI_A {
NSObject<GDAPI_C> c;
NSArray<NSObject<GDAPI_D>> d;
}
@protocol GDAPI_B {
}
@protocol GDAPI_C {
}
@protocol GDAPI_D {
}
以及调用服务
@protocol GDXXXAPI <NSObject>
/// 测验
- (void)test:(NSObject<GDAPI_A, GDAPI_B>)a;
这个在落地进程中坑的确非常多。
B 模块想创立 A 模块的模型
当然这个是很不合理的,但现实中的确许多这样的前史问题。
当然也不能用模型下沉开倒车,那处理上用了一个巧劲
/// 创立 XX
- (XXXModel *)createXXX GDM_EXPORT_MODULE();
供给一个创立模型的 API 给外部运用,这样关于 Model 的管理仍是在模块内,外部模块运用上从 new XXX()
改为 [GDAPI.XXService createXX];
即可。
零零碎碎
用正则判别抓取 AST,在一些二三方库中也是很常见的,但来处理 OC
的确挺痛苦的,再加上前史代码许多没什么规范,空格、注释林林总总,写个通用的适配算是比较耗时的。
还有便是一些个性化的兼容,也存在一些硬编码的情况,比方有些组件去关联到的 Model 在 framework 中,保护一个对应表,用 @class
来兼容处理。
后续
篇(jing)幅(li)有限,就不再展开阐明,这个完成思路影响了笔者后续的许多开发进程,有爱好能够看下笔者 Flutter 的文章,里面也是 codegen 的广泛运用。
假如有任何问题,都能够评论区一同讨论。
手敲不易,假如对你学习作业上有所启示,请留个赞, 感谢阅读 ~~