iOS解耦合-你做到了吗?
你经历的项目有多大,别人不敢想
一天的coding,你经历过多少次编译调试
xcode一天经历多少次crash
你的架构师卷你了没
你抱怨你的mac老旧了没
具体iOS解耦方面,看过同仁发布的一些博客,重在工具原理居多,但具体操作并未提及,讳莫如深
具体解耦执行阶段,编译层面问题,运行库符号问题上,我还未发现细丝入微的博文出现
当然,解耦方面也借助于工具,但工具是死的,在项目执行阶段,我们能做到什么样的程度,各有各的方法论
如果你有这方面的困惑,希望这篇文章能对你有帮助
一、解耦之路
-
小白单工程,什么都往里塞
-
模块化,一个变两个,两个变4个
- 横向分层,业务层纵向梳理
- 大家还是要凑在一个项目里维护自己的模块
- 各自模块依赖主工程
- 模块间的管理还是会发生冲突
-
插件化
- Cocoa Pods/Maven
- 模块剥离为独立工程,单独做版本管理
- 隔离开团队/项目之间的相互依赖 彼此独立开发
- ...
但是
,但是还不能解决独立编译问题,依赖还是很严重
-
组件化
- 解决独立编译、运行、调试
- 独立插件不再是一稳定版本号,而是一个稳定的二进制包
从小白一路走来,未觉自己现在多高阶,只觉当初多无知无畏
1~4
,不知会否共鸣作为开发者的你
- 曾几何时,插件化之后自己一度感觉很满足
- 但是,越往后来,源码越来越多,越来越烦,总看不惯别人的代码,一个最明显的感觉,慢,非常的慢,超级慢,上万个资源,Xcode索引就是个无底洞
- 硬着头皮,无论如何要做切割,组件化独立编译,避无可避
- 当下,你是否解决了组件化独立编译发布问题呢~~~
二、解决源码依赖,从物理上切断头文件依赖
最明显的一点,import 头文件引用,曾经还一度傻傻的在考虑预编译头文件问题
项目的过度耦合,很重要的一个原因就是通过声明头文件找到方法(函数)入口,少了头文件,编译不过
有3种方式可以依循
-
url route路由模式,根据url schema规则解析行为,需要定义规则
-
target-action方式,定义字符串规则
- 字符串命名包括 target部分,action指令部分,也就是解析字符串,找到组件的代理target,根据action指令从target获取action执行,获取到最终目标
-
基于接口的实现Service方式
- 每个组件对外声明service,在类加载阶段 和 app启动阶段完成service与目标类的绑定
三、解耦工具「BeeHive」
BeeHive是基于接口Service的方式实现
BeeHive为模块编排事件,通过模块注册,模块调用可以实现模块的通信
可是组件独立编译问题依然没有解决
image.png- Home,Comm,Live3个组件都依赖BeeHive,这是组件通信的基础
- 3个组件各自独立编译分别依赖AFNetworking,Masony,SDWebImage等其他三方库
- 主工程也需要依赖BeeHive,AFNetworking等
如果去掉各组件的外部依赖,问题变得相当简单,我们可以利用Home,Comm,Live各自的framework集成到主项目里,没有任何编译问题
但是,这样操作,我们的组件化将变得毫无意义,组件如果没有外部组件BeeHive,AFNetworking的依赖支持,源码间的依赖又成了问题,而且我们剥离的这个组件不再是业务模块组件
总之 这会使得我们的具体业务模块组件化变得毫无意义
四、千呼万唤始出来
BeeHive的详细原理介绍与具体使用不再此博客的讨论范畴
-
创建组件很easy,pod lib create 组件命名
-
重新配置组件
-
打开配置文件 - 组件名字.podspec
-
配置组件frameworks依赖
s.vendored_frameworks = [ 'private/AFNetworking/AFNetworking.framework', 'private/BeeHive/BeeHive.framework', 'private/Masonry/Masonry.framework', 'private/YYModel/YYModel.framework' ]
-
s.vendored_frameworks也可以配置本地,也可以配置为仓库
- 组件要以二进制的方式被集成进主工程,所以依赖库的配置不能是源码依赖
-
-
利用测试工程Example pod install,build
-
IFLHomeModule动态库沙盒路径:~/Library/Developer/Xcode/DerivedData/IFLHomeModule-bzppajsbtgsrwcadgkvcpplldsvs/Build/Products/Debug-iphonesimulator/IFLHomeModule/IFLHomeModule.framework
-
BeeHive动态库沙盒路径:~/Library/Developer/Xcode/DerivedData/IFLHomeModule-bzppajsbtgsrwcadgkvcpplldsvs/Build/Products/Debug-iphonesimulator/BeeHive/BeeHive.framework
-
很明显,最终编译的组件
与依赖的三方组件
存在于 设备 Debug-iphonesimulator 目录位置
说明:
-
剥离业务组件编译时,读取本地三方依赖动态库,最终都会被拷贝到 设备 Debug-iphonesimulator 目录位置
-
我们只要保障业务组件依赖的三方库跟业务组件库一起集成进主项目,编译,
直接集成 IFLHomeModule.framework BeeHive.framework 到主工程就可以了
你会在 .app里找到 业务组件最终的 二进制文件
- 😊就是如此简单,组件的独立编译发布问题解决
- 📃依托于BeeHive
- 解决了组件之间的耦合问题
- 解决了业务组件之间的通信问题,这样操作没问题,一般不建议业务组件之间横向直接依赖
- 解决了主工程与业务组件的耦合与通信问题
有兴趣可以测下主工程编译时间,一定会大大出乎你的意料
🌟🌟所有的业务组件都是二进制,你自己的组件不再需要依赖工程源码环境进行编译调试,编译效率指数级改观
五、想当然了,依赖framework的import问题
目前的主工程状态:
-
组件独立编译问题解决
-
组件依赖三方库编译framework解决
-
组件framework集成到主工程,并且主工程正常访问组件,同时组件与主工程均对三方库的依赖问题解决
组件化到这一步已经是一个不错的里程碑,除了,主工程里对组件的import依赖
是否可以解决,最起码我们期望后续的开发
- 能否做到物理去除组件framework,项目运行不崩溃
- 或者不改变源码逻辑,仅仅拖拽组件到主工程里,能即时正常恢复之前使用过的组件
只有确定无疑的结果才是可信的
这里需要解决两个问题
- 访问组件不通过import
- 组件与主工程均依赖的关键三方库,不通过主工程import
比如在主工程里 #import "IFLHomeModule/IFLBaseViewController.h"
我们要去除掉这行import,通过BeeHive实现对HomeModule组件的正常访问
你还得保障 组件framework embedin,即 general - Embedded Content - 组件库选择 Embed, 否则相应的组件里符号你找不到
由于你的组件 根 你的主工程都会依赖三方库,比如 都依赖 BeeHive
- 这时候如果你import BeeHive,就会出现 sub module 未声明问题
- 通过BeeHive api获取组件目标类,编译器又会报错,因为没有import 组件模块,BeeHive的声明就会出现冲突,这是编译器的逻辑
- 因为组件与主工程都依赖BeeHive,而此时没有import组件,仅import BeeHive,编译器会认为BeeHive应该此target之前完成声明import,也就是在组件里
为了避免编译器逻辑冲突,避免组件的显示import
//#import "IFLHomeModule/Eservice.h"
//#import "BeeHive/Beehive.h"
- (void)enterHomeModule {
Protocol *serviceProtocol = NSProtocolFromString(@"IFLMVVMServiceProtocol");
NSLog(@"serviceProtocol = %@", serviceProtocol);
if (!serviceProtocol) {
NSLog(@"目标组件 - homeModule组件不存在..");
return;
}
// id destVc = [[BeeHive shareInstance] createService:serviceProtocol];
Class _BeeHive = NSClassFromString(@"BeeHive");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if ([_BeeHive respondsToSelector:@selector(shareInstance)]) {
id _BeeHiveInstance = [_BeeHive performSelector:@selector(shareInstance)];
if ([_BeeHiveInstance respondsToSelector:@selector(createService:)]) {
id destVc = [_BeeHiveInstance performSelector:@selector(createService:) withObject:serviceProtocol];
if ([destVc isKindOfClass:[UIViewController class]]) {
[self presentViewController:destVc animated:YES completion:^{
}];
}
}
}
#pragma clang diagnostic pop
}
此时,如果移除组件,工程没有影响,只是进入组件入口就此中断了
参考项目 ModuleImpl