iOS 模块化进阶整理记录(上)

简介: iOS 模块化进阶整理记录(上)

先说模块化可能给项目带来的改变:


  • 代码提交更规范,分工更为明确,质量提高
  • 编译加快
    在原模式中,需要 150s 左右整个编译完毕,然后开发人员才可以开始调试。而现在组件化之后,某个业务组件只需要 10s ~ 20s 左右即可开工
  • 结合 MVVM
    更加细化的单元测试,提高代码质量,保证 App 稳定性
  • 回滚更方便
    面对发生业务或者 UI 变回之前版本的情况,以前我们都是 checkout 出之前的代码。而现在组件化了之后,我们只需要使用旧版本的业务组件 Pod 库,或者在旧版本的基础上再发一个 Pod 库便可。
  • 上面都是忽悠你来看的,别当真 🤣


模块化抽离


最近一直在调研模块化的相关知识,基本掌握了“初级封装抽离”的水平,正在迷茫之际,遇大神指点迷津,探索出了后面的进阶路线,心中默默感谢大神一刻钟...


1)初级封装抽离:

主要工作就是把 App 之间重用的 Util、Category、网络层和本地存储等抽成了 Pod 库,由于三方库自带一定的解耦性,对后期的组件化开发也比较有帮助。另一方面工作比如Chart,ChartSocket这些功能在各个 App 之间重用的却不会过于耦合,所以拆分难度也不会太高。


这一级的抽离相对简单,难点倒是对 cocopods 等工具的使用,目前的我组件化学习就只到这个水平,大家共同学习!

相关组件化工具的使用参考:《使用 CocoaPods 对公有库开源和私有库组件》https://juejin.cn/post/6844903581250748430

都是自己摸着石头过河,有什么不对的地方,大家探讨哈~


2)中级解耦抽离:


以 Analytics 统计功能为例,Analytics 是依赖 UMengAnalytics 来做统计的,用于收集数据的方法处理不好极易发生耦合,如既依赖了 User,还依赖了 currentServerId等。

应对 Analytics 这类情况,网上资料有几种方法来解耦:


  • 1.把它依赖的代码先做成一个 Pod 库,然后转而依赖 Pod 库。有点像是“依赖下沉”。
  • 2.使用 category 的方式把依赖改成组合的方式。
  • 3.使用一个 block 或 delegate(协议)把这部分职责丢出去。
  • 4.直接 copy 代码,其实我首先想到的就是这个 😂,copy 代码这个事情看起来很不优雅,但是它的好处就是快。对于一些不重要的工具方法,也可以直接 copy 到内部来用。


对于解耦,网上类似的资料还有利用中间件 Mediator的方式:


image.png


应对上面的情景,最直接的方法就是增加一个中间件,各个模块跳转通过中间件来管理。这样,所有模块只依赖这个中间件。


但是中间件怎么去调用其他模块那?好吧,中间件又会依赖所有模块。好像除了增加代码的复杂度,并没有真正解决任何问题。


image.png


有没有一种方法,可以完美的解决这个依赖关系那?

我们希望做到:每个模块之间互相不依赖,并且每个模块可以脱离工程由不同的人编写、单独编译调试。

下面的方案通过对中间件的改造,很好的解决了这个问题,解决后的模块间依赖关系如下:


image.png


实现方案 demo 源码地址: https://github.com/zcsoft/ZC_CTMediator,搞来学习吧

目录结构:


image.png


所有模块的引用关系如图:



image.png


由于 demo 中只是从 ViewController.h.m 中跳转到 DemoModule 模块,所以只需要 ViewController.h.m 依赖 CTMediator,CTMediator 到 DemoModule 模块的调用是使用运行时完成了(图片中的蓝线),在代码中不需要相护依赖。

也就是说,如果一个模块不需要跳转到其他模块,就不需要依赖 CTMediator。

完整的内部调用关系图:


image.png


响应过程:

1.ViewController 中判断Cell选中
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
-> 
2.CTMediator+CTMediatorModuleAActions 中图片加载响应方法
- (void)CTMediator_presentImage:(UIImage *)image;
-> 
3.CTMediator 中本地组件调用入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;
->
4.Target_A
- (id)Action_nativePresentImage:(NSDictionary *)params;
完成跳转。

事件响应断点:


image.png


Demo 中各个文件功用说明:

<1>CTMediator.h.m 功能: 指定目标(target,类名)+动作(action,方法名),并提供一个字典类型的参数。

CTMediator.h.m 会判断 target-action 是否可以调用,如果可以,则调用。由于这一功能是通过 runtime 动态实现的,所以在 CTMediator.h.m 的实现中,不会依赖任何其他模块,也不需要知道 target-action 的具体功能,只要 target-action 存在,就会被执行 target-action 具体的功能由 DemoModule 自己负责)。


CTMediator.h 里实际提供了两个方法,分别处理 url 方式的调用和 target-action 方式的调用,其中,如果使用 url 方式,会自动把 url 转换成 target-action。


<2>CTMediator+CTMediatorModuleAActions.h.m 功能:CTMediator 的扩展,用于管理跳转到 DemoModule 模块的动作。其他模块想要跳转到 DemoModule 模块时,通过调用这个类的方法来实现。


但是这个类中,并不真正去做跳转的动作,它只是对 CTMediator.h.m类的封装,这样用户就不需要关心使用CTMediator.h.m跳转到DemoModule模块时具体需要的target名称和action名称了。


<3>‘CTMediator.h.m’+‘CTMediator+CTMediatorModuleAActions.h.m’ 共同组成了一个面相 DemoModule 的跳转,并且它不会在代码上依赖 DemoModule,DemoModule 是否提供了相应的跳转功能,只体现在运行时是否能够正常跳转。

至此,CTMediator 这个中间层实现了完全的独立,其他模块不需要预先注册,CTMediator也不需要知道其他模块的实现细节。唯一的关联就是需要在 ‘CTMediator+CTMediatorModuleAActions.h.m’ 中写明正确的 target+action 和正确的参数,而且这些 action 和参数只依赖于 Target_A.h.m。


action 和参数的正确性只会在运行时检查,如果 target 或 action 不存在,可以在 ‘CTMediator.h.m’ 中进行相应的处理。既:CTMediator 不需要依赖任何模块就可以编译运行。

<4>Target_A.h.m 提供了跳转到 DemoModule 模块的对外接口,与 CTMediator+CTMediatorModuleAActions.h.m 相互对应,可以说它只用来为 CTMediator+CTMediatorModuleAActions.h.m 提供服务,所以在实现 CTMediator+CTMediatorModuleAActions.h.m时只需要参考 TargetA.h.m 即可,足够简单以至于并不需要文档来辅助描述。其他模块想跳转到这个模块时,不能直接通过 Target_A.h.m 实现,而是要通过 CTMediator+CTMediatorModuleAActions.h.m 来完成。

这样,就实现了模块间相互不依赖,并且只有需要跳转到其他模块的地方,才需要依赖 CTMediator。


<5>DemoModuleADetailViewController.h.m DemoModule 模块的主视图,这个例子中,会从 ViewController.h.m 跳转到这个模块。

<6>AppDelegate.h.m APP 入口,从应用外通过 Scheme 跳入程序时会经过这个类。

<7>ViewController.h.m APP 主视图,需要在这里跳转到 DemoModule 模块。


3)高级初始化抽离:

AppDelegate 充斥着各种初始化和第三方的注册,这些初始化会被各个业务组件使用,而且第三方库基本都需要注册一个 AppKey ,特别是一些第三方的库需要在 application: didFinishLaunchingWithOptions: 时初始化。

面对这种高难度的耦合场景,我想到了一个基于 runtime 的 AOP 解决方案。

关于AOP的简单介绍参考: 《基于 Aspects 简单展示 AOP 面向切面编程(中英文)》https://juejin.cn/post/6844903560837087246

原理就是利用 runtime,不需要在 AppDelegate 中添加任何代码,就可以捕获 App 生命周期,具体的解决方案还有待探讨。

这里引用《iOS App组件化开发实践》的解决方案,通过创建一个 PBBasicProviderModule 弱业务组件:

  • 它通过依赖YTXModule来捕捉App生命周期。
  • 它来负责初始化自己的和第三方的东西。
  • 所有业务组件都可以依赖这个弱业务组件。
  • 它来保证所有东西一定是是初始化完毕的。
  • 它来统一管理。
  • 它来暴露一些类和功能给业务组件使用。


目录
相关文章
|
Android开发 iOS开发
iOS 逆向编程(四)实操 Jailbreak 进阶必备软件
iOS 逆向编程(四)实操 Jailbreak 进阶必备软件
105 0
|
缓存 JavaScript iOS开发
iOS 逆向编程(十五)Cycript 语法进阶(封装 .cy 脚本文件)
iOS 逆向编程(十五)Cycript 语法进阶(封装 .cy 脚本文件)
191 0
|
Android开发 iOS开发
iOS 逆向编程(四)实操越狱进阶必备软件
iOS 逆向编程(四)实操越狱进阶必备软件
109 0
|
存储 前端开发 测试技术
iOS 模块化进阶整理记录(下)
iOS 模块化进阶整理记录(下)
186 0
iOS 模块化进阶整理记录(下)
|
Android开发 iOS开发 开发者
iOS 模块化进阶整理记录(中)
iOS 模块化进阶整理记录(中)
308 0
iOS 模块化进阶整理记录(中)
|
29天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
5天前
|
iOS开发 开发者 MacOS
深入探索iOS开发中的SwiftUI框架
【10月更文挑战第21天】 本文将带领读者深入了解Apple最新推出的SwiftUI框架,这一革命性的用户界面构建工具为iOS开发者提供了一种声明式、高效且直观的方式来创建复杂的用户界面。通过分析SwiftUI的核心概念、主要特性以及在实际项目中的应用示例,我们将展示如何利用SwiftUI简化UI代码,提高开发效率,并保持应用程序的高性能和响应性。无论你是iOS开发的新手还是有经验的开发者,本文都将为你提供宝贵的见解和实用的指导。
86 66
|
16天前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
|
20天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
22天前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
下一篇
DataWorks