一、前言
什么是组件化
组件化就是将APP拆分成各个组件,然后通过主工程将项目所需要的组件组合起来,比如首页,个人中心,某个复杂的二级页面,都可以作为组件,同时解除这些组件之间的耦合。这样组件化过后的项目就变成了很多小组件,如果新项目中有类似的需求,直接将组件引入稍作修改就能使用了。其实就是把我们开发的组件变成一个第三方库引入,我们以后开发就相当在维护一个第三方库
组件化的优点:
- 组件可独立运行,提高的代码的复用性,组件化的颗粒度越细,可复用度就越高。
- 当组件库的数量足够庞大时,项目只需要组合组件即可完成大部分的开发工作。
- 组件化后项目的代码结构更加清晰,追踪问题、修复bug、增加需求更方便
- 不同业务组件相互独立,明确团队开发的业务边界,增加团队协作效率
组件化的缺点:
- 增加开发人员的学习成本
- 增加了代码的冗余,组件化颗粒度越细,中间代码越多
- 增加了项目的复杂度,复杂度越高越容易出问题
- 项目复杂、臃肿、庞大,编译时间过长
下面说说我的一点积累,会有很多不足,多多包涵。
- 并不是所有工程都适合组件化,如果你的工程规模不大的或单人开发,都不建议组件化,反而降低你的开发效率。如果工程规模大,有多个APP业务有重复的,团队开发的,组件化确实助力很大。
- 组件的拆分,不是所有页面都要做成组件的,一个工程有200多个页面,不可能有这么多的组件,只是某一个大功能会成为一个组件,比如首页组件,个人中心组件,某个功能的二级页面,基础组件(网络请求等 各种封装),一个组件内肯定有多个页面和功能的,这个根据自己公司的业务模块来进行合理的划分即可。
- 组件是一个单独工程,在没有任何交互的情况下可以独立运行,但这是理想化。一个工程一定会有网络请求,会有一些和其他组件工程共用的类或文件。这些东西不能每个组件都写一遍,所以要有一个基础组件存放网络请求,公共类,图片等。所以组件单独运行实际情况是:业务组件+基础组件 才能运行
- 组件化一般是有一个主工程(壳子)和很多子工程组成,子工程的管理可以使用Workspace ,Workspace是xcode 自带的一种工程方式,多个工程存放在一起,一个Workspace管理多个子工程,这简直就是为组件化而设计的,如果不使用Workspace管理子工程,子工程要每个都要podspec 文件,还是麻烦。Workspace只创建一个podspec 文件就可以了。
- 路由或中间件,没接触组件化的同学很多都认为路由或中间件就是组件化 或者是组件化的最重要的,其实路由或中间件只是组件化的一个跳转通信的工具,我觉得怎样把组件搭建起来才是最重要的,因为可选择的通信工具很多,很多大神都写过各种思路的路由或中间件,我们demo选择具有代表性的 MGJRouter和CTMediator 同时集成给大家展示,大家组件化成功后,可以更换其他路由或中间件,试试不同的通信思路。
二、创建Workspace
- 工程的创建
TestApp
TestAppModule
在github或码云上创建工程 并clone本地工程,或本地创建在上传。组件都是私有库的,不对外暴露,我这个是demo都是公开库了
TestAppModule 为各组件集合的工程 (Workspace)
TestApp 为主要工程(俗称壳子)最后所有的组件会在这个工程进行联调,并打包。
-
先从TestAppModule 组件的工程 开始
创建Workspace 名为 TestAppModule
存储在桌面的TestAppModule 文件夹里
在桌面的TestAppModule文件夹下创建多个项目工程 例:TestA TestB...
打开TestAppModule.xcworkspace,点击左下角+号 添加TestA等工程到xcworkspace
要选择工程的xcodepro 添加进来
最终是这样的
三、配置组件工程
组件化大家的印象中,单个组件是可以独立运行的,互不干扰,但是有的公共方法如 网络请求,共用的宏,图片等,如何处置。这时候就需要一个Basis组件来放置,每个组件都可能使用的公用类都要放在Basis组件里,Basis组件也负责所有pods第三方的引用,所以每次运行组件并不是单独的,应该是:TestA+TestBasis 才能运行。
-
TestBasis工程里 举例 如下配置,一些公共类不用细说,有变动的是图片读取,因为图片会跨组件读取(跨工程),正常图片读取 [UIImage imageNamed:@""]是不起作用的,这里写了个NSBundle的分类 ,后面会有调用
TestBasisHeader 是对暴露的类,里面包括所有公共类,第三方,路由URL,图片路径,接口文件等。test工程 因为引用的少我都写在一起了(2019.12.9更新 ,基础组件内的 某个功能可单独集成,具体配置看组件工程的podspec文件和Podfile文件)。
TestA 等其他工程里如下配置,因为我们同时使用了MGJRouter和CTMediator 演示,出现了两种通信类
-
podspec文件
本地工程创建podspec文件
cd到TestAppModule文件夹目录 pod spec create 工程名
和工程 xcodepro 一样添加进来
这时候可以先往github上传一次,因为下面我们配置Podfile和podspec文件 里面都要写github地址
podspec 配置十分重要,如果我们要集成第三方
重点:Podfile 文件进行配置 ,podspec也要改动
podspec里面的东西比较多 我只截取一部分,更多去工程TestAppModule
看
Podfile 文件创建
本地工程创建Podfile文件
cd到TestAppModule文件夹目录 vim Podfile按下图编辑完
和工程 xcodepro 一样添加进来
配置好了 我们pod install ,这个步骤可能会有多次的不成功,因为Podfile 文件和 podspec文件 都要写对
再次重申:Podfile 文件进行配置引用第三方 ,podspec也要对应改动
成功后下面打开工程先运行TestA 看看TestBasis与其他工程之间是否关联上
TestA 引用TestBasis 工程里的TestBasisHeader.h类
看到log 打印了,证明我们的业务组件+基础组件的模式成功了
这里要注意一点 我们在配置podspec文件的时候指定了每个组件最后到壳子工程上的文件,只有在Classses文件下的类才能集成进壳子
TestA 为例Classses文件 下的类才才能被集成的壳子内,其他的不行,但是每个组件单独运行时没有区别,所以ViewController可以引用TestBasisHeader.h,并使用TestBasis的方法。但无法集成到壳子工程内。
Classses文件 的pch 要解释一下
这个文件用不用都可以。pch文件在这的作用就是解耦,可以看到我把TestBasisHeader.h 文件放到这里,TestA工程内都可以使用了。这是优点,弊端也很明显引用TestBasisHeader.h 相当于大量的头文件和宏定义放到pch里边,导致编译时间过长。到后期壳子编译会很慢。
如果 你的某个组件没有使用pch,在组件里podspec 文件,要把对应的 prefix_header_file 注释掉。否则主工程pods会报错
我先在各工程中简单的布局,
因为我们同时使用了MGJRouter和CTMediator 演示,出现了两种通信类
MGJRouter需要在 TestARoute.m、TestBRoute.m、TestCRoute.m中把路由注册上。
CTMediator 需要 Target_TestA.m、Target_TestB.m 、Target_TestC.m中配置对应的类
这时候我们可能会添加新的文件,类,或图片,需要git上 save 并push,
如果TestBasis 创建新的文件,并且别的组件需要使用这个新的文件,会引用报错,在save 并push之后 要pod install,别的组件才能使用。pod install这个操作在组件化中很频繁。
如果之后运行 还出现引用报错,未必是真的。可以(command+shift+k ) clean一下。clean这个操作 在组件化中经常用到,当我们添加一个新的文件,类,或图片在pod install 后都可以 clean一下再运行。虽然我们把路由都配置完了,但是组件这个工程路由是无法进行跳转的。下面我们进行主工程(壳子)的配置。
四、主工程的联调
-
TestApp 是我们的主app工程,最后所有的组件会集成到这个工程进行联调,并打包。
我们在主工程创建TabBarController 引用TestA TestB TestC,这个TabBarController,我没有设计成组件,因为也没几行代码,如果工程很大有多个TabBarController,可以考虑根据业务模块来进行合理的划分。
Podfile文件
可以看到 主工程pod 引用组件是本地的路径,并不是pod私有库的形式。这样的好处是,当你在组件更改代码主工程同步生效。在开发阶段建议使用本地的路径。稳定之后可以把组件pod私有库,在进行引用。
编写完毕 pod install -
这个很大几率会不成功,因为组件工程的Podfile文件 和podspec 里面如果有不符合就会报错,比如我就遇到了 我的组件工程的podspec文件 只有testA有pch文件 但是testC工程编写了c.prefix_header_file = 'TestC/TestC/Classes/TestC.pch'',但实际我的工程里没有这个TestC.pch文件,就报错了,把这行注释掉。重新pod install 成功
我可以看下内部结构
上面讲过 我们的组件是在集成了每个工程的 Classes文件下的所有文件,所看起来以非常整洁。
接下来我们创建TabBarController 并引用组件的类最后运行起来
五、pod 集成 本地framework(2021.7.9更新)
-
遇到了第三方sdk 是framework,并本地集成
如果是常规工程按照文档集成是没问题的。但是组件化会有问题,一个组件使用该sdk,按照文档集成,组件是没问题的,但是主工程在集成该组件无法找到对应sdk.。
正确的做法是 为该sdk 创建一个组件,并查看framework sdk自带的podspec
把这段复制出来 拷贝到我们组件的 .podspec上
这样我们需要使用sdk的组件工程引用 该组件pod install ,主工程在pod install 就可以了
- SDK自带资源文件和图片的读取出来
wb.ios.resource 这个需要重点说一下,因为sdk 里面有很资源文件和图片等等,按照我们之前说的 组件化读取文件和图片系统方法 比如正常图片读取 [UIImage imageNamed:@""]是不起作用的,都需要使用组件的路径读取。资源文件也是一样。framework sdk内使用的获取文件的方法是
NSString *filePath_= [[NSBundle mainBundle] pathForResource:@"file" ofType:@"txt"]; ,使用的路径都是[NSBundle mainBundle] ,虽然知道这么写的但是 framework都是闭源的,无法改代码,导致使用sdk 图片和文件都读取不到。 wb.ios.resource作用是指向该组件下的sdk里的 bundle文件,可以使用[NSBundle mainBundle],bundle包含了图片和 资源文件。
六、git冲突及仓库的容量问题
基于cocoapods组件化开发,pods命令,非常频繁,团队内时有互相冲突,不同于开发文件的冲突,pods冲突是10多个配置文件冲突,十分不好解决,
解决方案有三种,
- 最笨方法10多个配置文件 一个一个的手动解决冲突,
- 在团队内还没有异常的队员项目目录里找到Pods.xcodeproj,显示包内容 复制project.pbxproj,传给我们,我们可以粘贴覆盖解决,
- 我们可以使用.gitignore主要是过滤pod文件夹下的文件不在上传git,从根本上解决冲突问题,但是我们podfile文件和podspec文件 是提交的git的,这样团队其他人还知道第三方的变动,而不用担心pod的变动冲突问题。
使用.gitignore 也可以解决仓库爆仓的问题。一般组件化开发的,业务多 ,代码量基本都很大,在使用的某个组件仓库爆仓了。一般我们使用github或者码云 ,的单个仓库上限是一个1个G的容量 ,仓库包含了 组件本身代码和pod的第三方库还有git提交的历史记录,随着时间增加仓库爆仓了,1个G 满满的,无法在提交。所以我们重新创建了仓库,使用.gitignore 忽略pod文件夹,不在提交pod相关的第三方代码,提交仓库的只有我们自己开发的代码文件,这样节约了大量空间,基本上减少了80%以上的空间。 具体可以使用.gitignore配置忽略文件 ,这个文件网上有很多。就算你的git仓库是公司自己创建的,无上限,考虑到git冲突也建议你使用.gitignore过滤pod
七、最后说下注意事项:
- 如在组件内新建或删除 文件夹 、类、图片等,确保不出错之后,一定要git提交,这时候运行主工程会报错或加载不到 ,主工程要pod install ,如果是图片等资源文件还要clean,运行成功好也要及时的提交git,因为组件化一般都是多人团队开发,git 操作冲突了,会很伤神。
- 加载资源文件,如图片等原来方法都会失效,需要路径加载。
- 设计模式不限制 MVC,MVP,MVVM 每个组件都可以不一样。
- 有的组件未必使用基础组件内的某个功能,我的例子却一起引用了,这个解决很灵活可单独引用,也可在pod中操作单独集成,也可把基础组件再次拆分多个组件,根据业务模块来进行合理的划分即可。
工程地址
注意 因为GitHub 下载工程默认会加-master,导致主工程报错,是因为工程名变了,找到不组件的造成的,可以把TestAppModule-master 的 -master删掉,在主工程运行,如还报错 主工程 pod install。
TestAppModule 组件工程
TestApp 主工程