[周小鱼]& iOS 灵活的 模块化/组件化 工具与规范 Lotusoot 解说
public protocol AccountLotus {
|
2. Lotusoot
在各个模块中,实现 PublicModule 中对应的 Lotus,即具体的服务类,称为 Lotusoot。
Lotusoot 中具体实现了服务的逻辑,并在 注解 中表明了模块的 命名空间-@NameSpace、Lotusoot-@Lotusoot、Lotus-@Lotus。举例如下:
// @NameSpace(TestAccountModule)
|
注解是非必须的,注解是为了 Lotusoot.py 可以扫描 Lotusoot 自动注册,后面一节将会说到。如果你不想使用自动注册,也可以选择手动注册。
注:这里做一点解释,『协议-服务类』即『Lotus-Lotusoot』的命名由来纯属卖个萌,因为,协议是暴露外部的,所以叫莲花,而具体实现的服务类自然就是莲藕(Loutsoot)了。
3. 自动注册 or 手动注册
如果使用了注解,可以自动注册所有服务,只需要:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
如果先手动注册,如下所示:
[LotusootCoordinator registerWithLotusoot:[AppDelegateLotusoot new] lotusName:@"AppDelegateLotus"];
|
不过手动注册就是去了使用 Lotusoot 的意义了,所以在无法满足条件时再使用手动注册(比如目前 0.0.2 版本的 Lotusoot,主工程如果有多 Target,是无法动态获取 Target 名,导致无法正确获取命名空间,反射到类,当然除工程,各个模块由于使用 CocoaPos 不存在这个问题。下一个版本 Lotusoot 将会重点解决这个问题)
4. 关系图
通过 Lotusoot 搭建的工程如下图所示:
所有模块只需要依赖 PublicModule,通过 PublicMoudle 下的 Lotusoot 即可调用其他模块的服务。实例代码如下:
let loginLotus = s(LoginLotus.self)
|
OC 中使用:
id<LoginLotus> loginModule = [LotusootCoordinator lotusootWithLotus:@"LoginLotus"];
|
Lotusoot 是如何实现自动注册服务的?在什么时机?
这个问题也是在编写 Lotusoot 最初重点考虑的问题。原因是 Swift 没有 +(void)load。
大概是每个用 Swift 开发模块路由和解耦工具的人都纠结过的问题。
1. OC 中常见的解决方案
先说说通常 OC 的路由或者解耦,都是在 +(void)load 注册类服务的,大致的做法类似于:
+ (void)load {
|
或者
+ (void)load {
|
这样,即使是在每个模块内,也可以正常注册自己的服务。而主工程和其他模块调用只需要通过字符串调用即可。
2. Swift 中的痛点
由于 Swift 是没有 +(void)load 的,也没有其他可靠的方法可以替代,那么势必需要在主工程中加入注册路由这一步骤,通常可以放在 didFinishLaunchingWithOptions,因为主模块可以调到所有模块的类。那么随之而来的问题就是,你可能会出现这样常常的代码:
ServiceManager().register("LoginService", toService:LoginService.self)
|
可能会有一张长长的列表,而且由于服务都分散在各个模块,但是却集中在主模块,往往难以看到路由表和服务类的关联,表征不够明显、关系不够强烈。
3. Lotusoot 的解决方案
有什么更好的办法注册?mmoaay 给了我一个灰常棒的建议,参考 R.swift 的做法,通过脚本,来完成自动注册。
R.swift 的提供的功能是,可以让使用的 iOSer 可以像开发 Android 的一样调用图片、字符串、音频等等资源文件。在 Project 中插入 Run Script,这个脚本可以在编译阶段扫描整个工程,列算所有的资源文件,最后生成一个
R.generated.swift文件,就像这个样子:使用的时候就可以:
同样 Lotusoot 通过一个 python 脚本,在『Compile Source』之前扫描工程目录下的文件,找出 Lotusoot 和 Lotus 对应关系,并生成一个 Lotusoot.plist 文件:
如何识别 Lotus?
目前,Lotusoot 使用了很 Low 的方式,在注解中表明了模块的 命名空间-@NameSpace、Lotusoot-@Lotusoot、Lotus-@Lotus,脚本就可以识别。举例如下:
所以,didFinishLaunchingWithOptions 只需要一句,即可自动注册路由。
LotusootCoordinator.registerAll()
|
为什么使用脚本解决?是因为到目前为止,所有的解耦工具或者路由都是通过程序员手动去写代码添加路由,不管是在 + (void)load 中注册也好,在程序启动后注册也好,都是有程序员手动管理的。使用脚本是希望在编译阶段前,就准备好所有的『协议-服务类』对应关系表,在程序启动后通过这张表自动注册,实现程序员不手动注册、完全无感。我觉得这才是真正的解耦工具应当具备的功能。
Lotusoot 的重大缺点和下一版本目标
Lotusoot 的缺点是显而易见的,虽然通过脚本可以在编译阶段创建好『协议-服务类』关系表。但 Lotusoot.py 识别『协议-服务类』是通过注解来的,而这里的注解其实就是注释,不能编译检测错误,及时误写错也无法及时检查出问题。如果解决了这一痛点,就可以相对完美的解决了 Swift 的模块化方案。
目前的思路如下:
尝试通过全局方法或是其他语法方式实现真正的注解,可以像 Java 中的注解一样,不仅可以作为一种标识,也可以进行编译检查。
其实 OC 中是可以直接用宏定义做的:
#define Service(_name_) \
|
但 Lotusoot 是提供给 Swift 和 OC 以及混编项目都可以使用的,所以实现方案还需要我继续探索。
另一种方式可以使用 LLVM 是提供了 @annotation 操作的,如果通过这种方式生成 .plist 的注册列表文件应该会放到编译结束时。
以上,是以后的一些构思,希望可以完美的解决 Swift 模块化方案。如果你有什么好的建议,都可以来找我讨论哦~~~
Demo 和 Github
如果想更清晰的感受 Lotusoot 带来的模块化改造,请必须下载 Demo 来看哟。
总项目的地址在这里。
非常欢迎一起讨论(卖萌~~)
有什么问题都可以在博文后面留言,或者微博上私信我,或者邮件我 coderfish@163.com。
博主是 iOS 妹子一枚。
希望大家一起进步。
我的微博:小鱼周凌宇
T







浙公网安备 33010602011771号