组件、模块的区别和联系
组件:提供特定单一功能或则通用可复用UI;把一些可复用的UI元素或则单一功能,抽离为单个组件,便于项目的维护和开发。多个组件可以组合成组件库,方便调用和复用,组件间也可以嵌套,小组件组合成大组件
模块(模块组件):分属同一业务的代码进行隔离(分装)成独立的模块,可以独立运行,以页面、功能或其他不同粒度划分程度不同的模块,位于业务框架层,模块间通过接口调用,目的是降低模块间的耦合;把一些可复用的代码,抽离为单个模块,便于项目的维护和开发。可以调用组件来组成模块,多个模块可以组合成业务框架。
业务线模块(业务框架层):该业务线下关联的多个模块组件的框架层(虚拟的层)
为什么使用组件化和模块化?
开发和调试效率高:随着功能越来越多,代码结构会越发复杂,要修改某一个小功能,可能要重新翻阅整个项目的代码,把所有相同的地方都修改一遍,重复劳动浪费时间和人力,效率低;使用组件化,每个相同的功能结构都调用同一个组件,只需要修改这个组件,即可全局修改。
可维护性强:便于后期代码查找和维护。
避免阻断:模块化是可以独立运行的,如果一个模块产生了bug,不会影响其他模块的调用。
版本管理更容易:如果由多人协作开发,可以避免代码覆盖和冲突。
组件-模块关系示意图:
制作私有pod组件的过程
组件创建步骤
官方示例:https://code.tutsplus.com/tutorials/creating-your-first-cocoapod--cms-24332
参考文献:https://www.jianshu.com/p/7b4667cde80b
1、开个远程代码托管库,库地址未url
2、git clone url 到本地文件目录下path
3、在path目录下,执行pod lib create xxxx,创建pod模版工程
4、在pod模版目录文件中,找到xxx.podspec文件所在目录,修改配置下xxx.podspec文件(也可以不配置,等最后版本提交时再修改配置); 在和xxx.podspec文件同一层级目录下,有个xxx目录,这个目录中包含/Classes(存放.h、.m文件)和/Assets(存放图片、xib、音视频等资源,这些资源会打包在xxx.framework中;和xxx.podspec文件同目录下,有个/Example目录,打开xxx.xcworkspace工程,在Pods工程下xxx文件目录下,创建工程代码,每有了代码改动尤其是类的增加/减少、资源文件增减都要对/Example目录执行下pod install,不然,xxx.xcworkspace中可能引用不到对应新增/减少的类、资源,podspec文件官方详解
5、每次开发完,及时将代码提交到远程托管库;当这个版本的功能开发完后,修改校验xxx.podspec文件(发布版本时,必改项是版本号version),给版本号打tag,必须打tag,因为xxx.podspec中的s.version就是这个tag
$ git tag -a 0.0.1 -m “V0.0.1”
$ git push origin version号
$ git push —tags
6、验证.podspec是否配置正确
$ cd podspec文件所在目录
$ pod lib lint (验证)
$ pod lib lint —verbose (验证-并显示详细信息)
$ pod lib lint —allow-warnings (验证—忽略警告)
$ pod spec lint podName.podspec —verbose (这个从本地和远程验证你的pod能否通过验证,上面三个都是从本地验证你的pod能否通过验证)
7、至此一个pod组件制作完毕,但是当有多个pod组件时,为便于管理这些组件库,就创建了Spec Repo,来统一管理多个pod组件的代码库地址url;
// 创建Spec Repo远程私有托管库
$ pod repo add REPO_NAME SOURCE_URL
// 在本地cocoapods目录下查找REPO_NAME是否创建成功
$ cd ~/.cocoapods/repos/REPO_NAME
8、把xxx.podspec添加到Spec Repo中,这样多个pod组件库的下载就可以直接访问Spec Repo来下载对应的组件库了
$ pod repo push REPO_NAME SPEC_NAME.podspec
9、后续每次迭代开发提交,重复5、6、8的操作(后续迭代开发操作6可以省略)
10、随着版本的迭代,某一个功能可能会拆分成几个模块,这时就会用到subspec,将一个大组件拆分成几个小的子组件subspec,官网文献链接
资源文件的加载
不管是.a库,还是framework库,使用图片资源,都可以将资源放进bundle中存放,.a/framework库文件加载资源从bundle中取图片;
制作三方库在考虑Static Library格式的库库或者Framework格式的库时:依赖图片资源,使用Framework格式的库;想要拥有完整的依赖关系,使用Framework格式的库,此时外界可能需要剔除Framework格式的库之外的依赖库,而采用Framework格式的库内的依赖库,否则虽然不会产生依赖冲突,但会增加包大小
制作三方库或者使用三方库过程中,比如A组件和B组件命名了相同名字的类文件,会出现命名冲突报duplicate symbol错误的问题,对于制作framework或则.a组件时,命名规范,以库的前缀为类名,避免重名问题;
符号冲突文献:
https://www.jianshu.com/p/9cf63c30f650
https://www.jianshu.com/p/2591c0ec07f2
几种三方库制作过程mach -O Type选型区别:(build settings -> linking ->mach -O Type)
Executable: `静态库`,输出二进制
Dynamic Library:`动态非共享库`,输出动态链接库非共享库,程序`运行`时链接到`内存`,大部分场景下不可共享;app extension、部分macOS场景下可以共享
Bundle:`动态非共享库`,和Dynamic Library相近,不过需要手动调用函数加载
Static Library: `静态库`,输出静态链接库,程序`编译`时拷贝到`内存`
Relocatable Object File:`静态库`,和Static Library类似,但体积更小
cocopods官网说明资源加载有两种方式:
一、spec.resources、spec.resource,这种方式,资源文件会直接放置在组件所在的framework中(如下面的ResourceStyleSpec.framework),
资源路径查找方式:
1)获取当前组件的framework(也是一种bundle)的方式为:
NSBundle *bundle = [NSBundle bundleForClass:[self class]]
2)查找具体资源路径:
NSString *xxxPath= [bundle pathForResource:@"xxx" ofType:@"xib/png/..."];
二、spec.resource_bundles、 spec.resource_bundle,这种方式,资源文件会放置在组件所在的framework下的专门的资源bundle中(如下面的ResourceStyleSpec.framework下的ResourceStyleSpec.bundle)
资源路径查找方式:
1)获取当前组件XXX的framework(也是一种bundle)的方式为:
NSBundle *bundle = [NSBundle bundleForClass:[self class]]
2)获取当前组件的framework下的资源bundle:XXX.bundle:
NSBundle *sourceBundle = [NSBundle bundleWithPath:[bundle pathForResource:@"XXX" ofType:@"bundle"]];
3)查找具体资源路径:
NSString *xxxPath= [sourceBundle pathForResource:@"xxx" ofType:@"xib/png/..."];
相关资料:
https://www.jianshu.com/p/871e1b8891d8
组件的通信
一、组件的业务粒度划分
关于组件业务粒度划分,没有统一的标准,根据实际业务来界定组件提供功能服务的边界;比如支付组件,就是提供一个完整的支付流程的组件,外界只需要集成该组件,通过调用简单的组件提供的API就可以完成支付业务。
二、组件依赖方式确定
未组件化时,各业务模块耦合在一起的模块依赖关系:
通过上面三种方案的对比,可以看到:
业务组件和中间调度模块相互依赖时,中间调度模块依赖业务组件,当依赖了中间调度模块,也间接依赖了业务组件,并没有达到解耦的目的,是不合理的;因此,为了达到真正的解耦,应该是业务组件依赖中间调度模块,业务组件需要对外开发的实体,注册到中间调度模块中,外部通过调用中间调度模块来获取业务组件实体,外部并不依赖业务组件,从而达到解耦的目的,即依赖关系采用第三种-业务组件单向依赖中间调度模块的方案
三、组件间通讯技术方案实现
目前比较流行的组件间通讯技术方案有三种:scheme方式、protocol方式、target_action方式。三种方式都能实现组件间通讯,其中scheme方式和protocol方式都必须在运行内存中维护一个注册表,target_action方式不必维护这个注册表(当然使用者按需求想维护一个也是可以支持的),因此,从解耦的彻底性来看,target_action方式解耦更为彻底,但是组件间的通讯作为一个支撑所有场景下的组件间通讯方案,解耦只是其中一个的量化指标,组件通讯代码和组件调用链路代码可维护性和易接入性等也是量化指标,个人使用这三种方式的过程中,总结了这三种方式的选型场景,选择组件化方案时,可以适度参考一下几点:
1、纯iOS端组件间调用,使用protocol,可以传图片UIImage对象等非基础数据类型参数;但不能跨端使用
2、scheme方式场景一:系统通用跨端能力实现,比如短信链接、端外push、h5、浏览器、其他APP等跨端跳转到APP的某一个页面,就必须使用scheme方式,采用openUrl方式打开;
scheme方式场景二:APP内部组件间调用和组件内部调用使用scheme方式也是scheme的领一种使用方式
3、target_action方式,解耦比较彻底,完全利用系统的消息发送机制,不需要维护一个注册表,但也导致了一些特殊场景,比如需要拿到先前创建过的实例进行跳转,虽然CTMediator组件中提供了cachedTarget来缓存实例,但是这个实例是第一次调用CTMediator后才存储的,实际场景这个实例是外界创建的,这样使用CTMediator就拿不到这个外界创建的实例了。如果 CTMediator也维护一个外界注册的实例表,和protocol和scheme的方式差异也不大了
作为一个APP,尤其一个大型的项目APP,除了APP内部通讯,跨端通讯能力也是必须的,因此,不管是采用protocol还是使用target_action方式,由于牵涉到跨端通讯,因为都会使用系统通用跨端通信能力,即scheme的方式场景一。因为项目中,组件通讯方案都是protocol或者target_action方式结合scheme方式使用的,即在需要用到scheme的解析的case,根据约定的scheme字段,使用对应约定的protocol或则arget_actio去做组件间的通讯如跳转;用不到scheme的就按照protocol或者target_action方式进行组件间通讯。