最近一直在团队推动关于iOS模块独立运行相关的事项,想把最近的一些想法和实施状况经过这篇文章作一个记录。git
若是在一个项目中,某一块代码足够独立(功能、业务上),就会倾向于将他经过Cocoapods抽离为一个pods文件。经过一个podspec文件描述这个pod的信息。api
最直接的方式就是把相关文件组织好迁移到一个目录下,经过podspec对源码位置,资源位置等信息的描述,完成一个最简单直白的pod建立。bash
而且在Podfile中统统过网络
pod 'XXX', :path => '~/dev/XXX'
复制代码
这种方式,集成到主工程进行开发。开发自测完成后经过私有repo发布,而且将Podfile中的指向版本ide
pod 'XXX', '1.0.0'
复制代码
而后咱们就会说,抽离了一个库,项目的代码结构变得更合理更清晰了。工具
但这么作,在我看来跟在项目里直接用group把这些代码区别开来没什么区别,反而要修改的时候还要从新git pull、pod install变得更加麻烦了。单元测试
由于这么作被分离的代码没法独立运行,并且因为依赖不清晰,没办法共享给其余项目使用,致使这种分离方式是一种伪解耦,该有的益处没有展示出来,修改的时候反倒更加麻烦,这绝对不是咱们想要的效果。测试
由此引出对模块独立运行spa
我的心目中完美的Pod应该在这四个维度上作到最好。命令行
依赖足够清晰 清楚的描述本身的依赖情况。说到这个不得不吐槽一个iOS项目与Cocoapods结合以后一个奇怪的现象,就是Pod若是在主工程中经过path引入的,那么,在不声明清楚自身以来的状况下,可使用主工程内全部类,甚至是主工程的类,而且不会获得任何提示,这种状况很广泛,而且过后想要理清依赖的成本极高,这个Pod算是废了(没有了抽离Pod的意义了)。
方便共享给其余项目使用 个人理解,抽离Pod的很大一个目的不就是为了共享吗?若是不是为了这个目的,其实不必抽离Pod的,反而更加麻烦了,只跟一个项目绑死的Pod在我看来,彻底没有必要作成Pod,项目里放在Group就能够了。而为了能达到这个目的须要克服不少的困难。
方便快速修改验证 随着主工程愈来愈大,编译速度愈来愈慢,开发效率也在无形之中慢慢下降。Pod从主工程中脱离出来独立运行,单独编译,隔绝对主工程的依赖,彻底自给自足,这样代码编译量就会大大下降,达到开发效能提升的目的。
自身质量保障 在第三点的基础上,带上彻底针对这个Pod的单元测试UI测试,完美颗粒化代码的同时,还能很好的保障自身的质量,而且清晰易维护,这块若是配合Xcode Server,会发挥强大的优点。
然而要作到这些维度上的最好,须要克服不少问题,接下来慢慢道来。
相似AFNetworking、SDWebImage这样的功能型代码,分离的一个只须要确保本身的依赖清晰,被依赖的时候使用方便就能够了。而对于偏业务型的代码就不那么容易了,一般会有界面,还会有各类业务带来的附加产物,例如打点,例如网络库各类规则。
由于功能型代码自己对于以上四点门槛不高,我就不展开讨论了,主要仍是展开说一下业务型代码。
推动业务型代码独立运行过程当中遇到的一些问题列举:
业务代码须要用到 打点、网络等基本能力,背后每一个能力均可能牵扯出一堆间接依赖,但这些依赖跟这个Pod自己没任何屁关系,同时还会是否是的出现间接依赖不明确致使的编译报错问题。
做为已是独立可运行的Pod了,界面什么的都本身hold了,那么它必定还须要跟其余自己以外的几面进行交互,举个例子,一个Product的Pod,须要跳转到Order中的一个界面,或者Chat中的一个界面,而这个界面在代码层面根本不存在,要如何处置。
以前也提到了,一个依赖清晰的独立运行Pod若是被不当心path方式开发了一次,那么这个Pod会慢慢变废,下次运行可能就不能运行了,因此还要想办法要怎么不被path依赖。
还有一个比较头疼的问题是,随着业务迭代,某个冷门的独立运行Pod并无跟上脚步,其直接依赖的功能库在主工程都更新了,但它却全然不知,难道还要一个一个校对吗?
解决了基础能力的间接依赖,各类必要的直接依赖的间接依赖也会出现不明确而出现的编译失败问题,须要解决,确保只有代码api须要更新时才会编译不过,才是最爽的开发流程。
实施过程当中开发了两套工具来解决。均未开源,外网勿搜。下面介绍详细思路。
好不容建了一个独立运行Pod,要是不当心被不明真相的同窗用path开发了就糟了。如何避免被path模式开发呢?若是是源码模式下,实际上是作不到的。因此咱们把每一个独立运行的Pod的产物定位二进制库,静态动态均可,podspec层面就不容许指向源码,想要修改源码,只能经过独立运行的工程进行修改。
具体操做这里不展开了,若是podspec指向的是静态库,而没有源码指向则这个Pod理论上不可能不可被path模式开发。
而若是经过Cocoapods 官方建议的 pod lib create
方式建立,则Pod代码会存在于 Development Pods 下,而必须在podspec中指定源码路径,所以我修改了 pod lib create 的脚手架模板,将Pod代码直接放入项目的一个Group中,而Group对应产物是一个framework,podspec直接指向podspec,外加Universal打包脚本就能够啦。
有同窗有疑问了,那源码调试怎么办呢?这个不用担忧,既然Pod已经能够独立运行,有什么问题须要调试,是均可以在Pod工程中进行数据Mock来还原问题的,因此主工程只是用来集成,不须要考虑调试问题。
即使是特别特殊的状况,只在主工程能还原,那临时加一下podspec指向本地path作一下debug也是能够的。
Pod独立运行工程的一大诟病就是时间一长,工程就没法编译经过运行了,而且哪些依赖须要更新,须要详细对比,成本很是高,不少遇到这样状况就会放弃Pod工程。
为此gearmaker中集成了版本仲裁能力,经过hook pod命令,在pod install以前,计算出指定客户端主工程最新的依赖全集,在pod install时,在这个全集中找到仲裁版原本使用。
这样一来,pod install后的Pod工程全部依赖必然与主工程一致,只须要修改由于依赖更新带来的相关api变动便可经过编译正常运行,确保Pod工程不会腐败。
全部Pod只须要直接依赖ServiceProvider,由ServiceProvider来统一提供服务能力,包括Pod工程自己须要的任何能力。好比,路由、打点、网络、从另外一个模块获取数据,获取一个View对象等,均不须要依赖其余库,直接从ServiceProvider中经过内置的protocol来得到,并使用。
提供能力的一方对预先放置在ServiceProvider中的protocol进行功能实现,经过如下方式将自身的能力注册入ServiceProvider,便可为其余提供能力,而不须要依赖。
注册服务
[ServiceProvider registService:[XMUserTrack class] withProtocol:@protocol(UserTrack)];
复制代码
获取服务
id<UserTrack> ut = [ServiceProvider serviceWithProtocol:@protocol(UserTrack)];
复制代码
对于新的Pod建立,直接使用如下命令建立:
gearmaker <PodName>
复制代码
根据命令行提示进行建立便可。建立完成的脚手架直接提供了ServiceProvider,开发同窗直接从ServiceProvider中获取服务进行Pod开发,开发完成后经过Universal脚本生成framework上传到私有repo中定版本便可直接使用。