基于Cocoapods单工程多应用架构总结

我们产品目前需要进行多个产品分支的开发工作,包括一些有细微差异的分支应用以及Pad版本的开发,所以客户端这边需要进行重构来适应这种需求的出现,之前的那种Copy-paste的方式过于粗暴,已经无法适应于我们目前的发展。幸好,在重构之前听了一次阿里的技术分享,受益匪浅,收获很大,现场也跟阿里的架构师同学交流了很久,非常钦佩他们的开发理解和架构设计。正好这种经验也非常适用于我们目前的产品需求,就决定在这种需求起步阶段进行重构,以避免后续工作量的浪费。

在参考淘宝的方案,以及考虑到我们目前团队的状况之后,我们决定不像淘宝那样严格去做,毕竟淘宝团队的规模达到了百人级别,而我们目前所有的客户端人员加起来也只有3个人。。。不是一个量级,同时也不需要把代码编译为静态文件再合并。我们最后决定使用target + 基础模块拆分的情况来适应当前的开发,而不是把项目完全拆分为bundle,这样的成本过高,我们根本无法符合这样的工作量。


=================================================

应用架构

应用架构使用的工具是cocoapods和Xcode中的target,这个后面说到。这部分大致的思路主要参考淘宝大牛的演讲,以及下面引用中的2,8,9。


我发现对于APP架构的发展方向,其实大家的思路是一致的。首先是建立大量的基础库,这部分是独立于app和module之外的。然后在此基础上,使用独立的module来开发具体的功能模块,module是相对独立的,可以拥有自己独立的资源文件,自己独立的源码目录划分,甚至需要的时候可以单独打包为一个应用,比如facebook中相册功能,既是facebook app的一部分,又可以单独打包为facebook 相册应用。


对于各个moudule之间的解耦合,大家的思路其实也是相似的,就是使用自定义url来实现,统一由一个总线进行路由工作,同时这个可以扩展到跨平台。每个moudle都需要实现一个protocol,这个protocol中有对url的处理方法,以及其他一些处理方法。在做类似跳转的时候,所有的跳转都是通过自定义url来进行的。比如我现在要跳转到个人主页,那么要做的不是去初始化一个Controller,而是向路由总线中传入一个自定义url,比如showself://usercard/111,usercard会映射到controller,而111则会映射为一个参数。我们内部使用的是开源项目是JLRoutes,但是这个开源项目使用起来其实对于这种架构不是特别适用,需要进行重新架构才行。对于一些不支持的自定义url类型,比如低版本中,这时候淘宝做的是一个降级,即把这个自定义url跳转到一个webview页面来显示。


=================================================

CocoaPods

关于CocoaPods,我们使用的方式是,我们把基础类库包括Utility,UILib,BaseHTTP,LoginService作为单独的项目独立出来,然后在项目中使用Pod进行引用,这样的方式是为了后续iPad版本的开发,可以很容易复用基础类库,这样只需要简单进行UI层面的开发即可,同时如果项目有改进或者修改Bug,也能即使进行同步。其中使用的CocoaPods技术,在下面的参考文章中有详细讲述,尤其是唐巧的那篇文章,各方面的讲解很实用。当然,我最推荐的还是阅读官方的guide,非常简洁以及易懂,也不长,这个可能是pod技术自身很简单的缘故,比gradle要简单的太多了,但是又非常实用。

关于Pod的使用方式以及私有仓库,podspec文件的编写等内容,可以参考官方的文档,不再啰嗦,主要总结一下在进行架构的过程遇到的问题:


1. 关于静态库文件发布的问题

其实这个问题是比较简单的,可以直接参考官方文档podspec文件的编写,我当时遇到的问题是想要严格按照淘宝的方案进行实施,这样就需要使用静态文件进行合并,但是如何通过一个工程来生成这个target以及如何控制访问权限呢,后来才发现是我这边对CocoaPods的误解。如果要使用静态库进行合并,那就需要单独对静态库文件再单独建立一个git仓库以及独立的podspec,把编译好的静态文件放到其中,然后再引用这个仓库的podspec。我们的项目实际只是单纯用来管理各个源文件的依赖而已,没有必要做成这样。至于详细的做法,参考:http://blog.sigmapoint.pl/developing-static-library-for-ios-with-cocoapods/


2. 关于开发环境的问题

在解决完静态库文件的使用问题之后,我又遇到一个困惑,就是如何设置开发环境呢。我们并没有那种最小运行时环境,不能在完全开发并测试完成之后再进行合并。查了一下,发现了这个:http://blog.sigmapoint.pl/developing-static-library-for-ios-with-cocoapods/,原来Pod不止可以通过引用某个repo的某个podspec或者设置不同的git路径,设置可以直接使用本地文件路径来进行开发。Podfile中引用podspec时,直接使用:path,这样在主项目中修改文件时,同时子项目中的文件也会被修改,这些文件不是copy到主项目而是应用的。后续可以最简单通过这种方式进行开发调试,等开发测试完毕,再把项目独立出去。


3. 项目中-ObjC和-fobjc-arc冲突的问题

我们开发的基础类库中用到了一些Category来实现操作,这个使用需要在项目的配置文件"Other Linker Flags"添加-ObjC,但是很蛋疼,我们使用了一些第三方的类库,比如腾讯的SDK,这个SDK需要设置-fobjc-arc,这个时候,我们基础库的中的很多Category都无法使用。这个问题困扰了我一两个小时,后来想了想,既然我们觉得开源项目肯定会遇到这个问题,果然在SDWebImage项目中就到了解决方法,单独在"Other Linker Flags"中添加一个"-force_load $(TARGET_BUILD_DIR)/libPods.a"即可运行,参考:https://github.com/rs/SDWebImage;这里使用TARGET_BUILD_DIR在一段时间之后,突然无法使用,搜索了一下发现,需要将$(BUILT_PRODUCTS_DIR),参见问题7

------2014/10/24更新----------

在更新Mac 10.10之后,同时更新了pod,然后编译的时候总是提示找不到i386的错误,然后删除工程,重新使用pod update的过程中,发现会提示下面的问题,按照错误的提示,在工程中header search paths和other flag添加一个$(inherited)即可

基于Cocoapods单工程多应用架构总结_第1张图片


4. ARC与非ARC的问题

这个问题其实我觉得很早之前就应该杜绝的,Apple官方出的ARC这么优秀的技术应该普及的很好才是,但是做技术很极端,要不特别热爱新技术,要不就是特别守旧不肯更新知识。历史原因,我们内部也有一些这种非ARC的文件。这个编译成静态库文件合并还好,但是对于代码合并,还真是让我为难了一阵时间。参考:https://github.com/CocoaPods/CocoaPods/issues/589  ,在官方的讨论中发现了解决方法,使用subspec实现。参考一个开源项目的具体实现:https://github.com/typhoon-framework/Typhoon/blob/master/Typhoon.podspec  ,发现CocoaPods的确是非常实用和强大。

基于Cocoapods单工程多应用架构总结_第2张图片


5. 关于"pod update"命令运行很慢的问题

在启动后第一次运行pod update或者pod install命令的时候,系统首先更新podspec的git仓库,私有的仓库还好说,一般podspec的文件会很少并且在本地会很快更新下来,但是master仓库的podspec文件一方面很大,另外一方面国内github网络质量不是很好,所以会经常莫名其妙卡住。解决方法有两种:第一种,参考唐巧的文章中提到的,一般情况下,可以不需要更新仓库,使用"pod update --no-repo-update"命令,隔一段时间更新一次仓库即可。第二种,是使用github上面提到的一种方法,我也不太懂,但是这种方法的确有效,http://stackoverflow.com/questions/23006683/cocoapods-staying-on-analyzing-dependencies:

 $ pod repo remove master
 $ pod setup
 $ pod install


6. 提示"Found multiple values (`armv7`, `armv7s`) for the architectures (`ARCHS`) build setting for the `Pods` target definition. Using the first"的问题

这个问题是来自于我们工程中设置arch的时候,没有使用标准的arch,而是添加两个参数armv7和armv7s,而Pods在建立工程的时候,默认只会取出一个arch参数,我们这是两个,会导致第二个参数不会被添加。解决方法,把两个参数合为一个参数形式"$(ARCHS_STANDARD_32_BIT)"。参考:http://lanvige.github.io/2014/03/19/architecturs-in-xcode/,http://www.overacker.me/blog/2014/08/03/architecture-headaches-with-cocoapods


7. Xcode只在打包时出现链接问题"ld: file not found: /Users/xxxxx/Work/xxxxx/codes/xxxxx/DerivedData/xxxxx/Build/Intermediates/ArchiveIntermediates/xxxxx/InstallationBuildProductsLocation/Applications/libPods.a"

这个问题是我这边的的一个使用错误,在解决问题3的时候,参考了SDWebimge的方法,在other linker flag中添加参数"-force_load $(TARGET_BUILD_DIR)/libPods.a",这个地方会导致无法打包,修改为"-force_load $(BUILT_PRODUCTS_DIR)/libPods.a",这样就解决了问题。参考:http://stackoverflow.com/questions/14644122/cant-archive-on-xcode-cocoapods-linking-error


8. Xcode编译没有问题,但是pod repo push时总是提示"cocoapods specification does not validate"

这个问题,是由于执行"pod spec lint"有warning的项目,cocoapods不通过lint,这个时候可以通过在pod repo push命令添加"--allow-warnings"标识,这样对于有warning的项目同样可以通过pod的lint


=================================================

Xcode Targets

不得不说,xcode是一个强大的开发工具,即使Android studio目前也无法相提并论,我自己的感觉主要是两方面,一方面是工程配置的容易程度,xcode填写几个参数就可以设置编译参数,AS还需要gradle和官方插件的支持,另外一方面,对C/C++的支持问题,目前这个地方还是个弱点,写一些NDK太费劲。我们需要开发不同版本略有差异的APP,这个毫无疑问使用target可以很简单解决,在开发之前,我参考了一下唐巧的一篇博客,受益也挺大的,避免了一些不必要的坑,http://blog.devtang.com/blog/2013/10/17/the-tech-detail-of-ape-client-1/。这个中间还算简单,修改一下Copy的资源和编译的文件都是比较容易设置的。有一个地方可以提一下,就是如何识别当前的target来运行不同的代码,唐巧的实现方式是添加一个Config类来做,但是我们的项目比较简单,没有必要引入这个。后来看了一下,决定使用宏来解决这个问题。在Xcode的预编译宏处理的地方添加不同的宏,然后通过这些宏来决定编译哪些代码,这样就可以实现我们略微差异工程的实现。

基于Cocoapods单工程多应用架构总结_第3张图片


基于Cocoapods单工程多应用架构总结_第4张图片



=================================================

总结

最后总结一下,不得不说CocoaPods是一个非常强大的工具,适当应用,使我们后续不可能完成的开发任务有了一些简单的解决方式,让我们微型团队也能做到同时维护多个项目。我们使用CocoaPods来进行模块解耦拆分,为后续iPad版本的开发打下基础,同时也解决一个大家的不良习惯,写代码的时候不关心设计层次,比如经常在Lib层面写很多业务逻辑,这个问题说了很多次,但是每次都会有人不太遵守,只是为了自己尽快完成任务。使用Target可以让我们不用使用Copy --- Paste方式来管理多个应用的代码,设置不需要多个分支,这个方式大大减轻了维护难度。


===更新于20141205===

其实应用架构这部分,我是后来写的。之前之所以没写,是因为觉得自己对这方面的理解不够深刻,也并没有真正感觉到这个技术的魅力。我之所以做这个架构变迁,是因为我们规划的iPad版本的开发工作。在经历了iPad版本开发之后,我是彻底感觉到cocoapods的厉害之处。大大减轻了我们开发的工作量,基本上两个人三周就完成iPad版本的开发和提交,并且不是通过copy-paste这种比较low的方法做完的,也没有加班。虽然iPad版本的功能相对于iPhone版本很少,但是工作量也是很巨大的,毕竟核心功能较为复杂而且还是一个完整的产品。可是通过cocoapods,的确是让我们效率高了很多。


后续本来计划的架构变迁,是把更多的模块独立出来,以及让iPad和iPhone版本融合,最终只维护一套代码即可。然后像淘宝和facebook那样走向一种高度独立的水平,把基础类库进一步完善扩展。只可惜,随着工作时间的增加,与这边的老板的价值差异越来越大,自己的职业也难以有所突破,我决定离职。这个架构的实现只能推迟一段时间,等我后面有机会做其他的应用会去做这个事情。


技术的魅力就在于此,同样的工作,可以用很少的工作量也可以用很多的工作量。可惜的是,我无法将自己的想法在我自己一直在做的项目上实现,还只能黯然离开,不想了,该走的还是得走,没有放弃就没有的得到。


=================================================

参考资料:

  1. 淘宝iOS客户端架构分享:http://club.alibabatech.org/resource_detail.htm?topicId=153
  2. 一篇非常好的基于Cocoapods架构的文章,这也是我们后续的发展方向:http://dev.hubspot.com/blog/architecting-a-large-ios-app-with-cocoapods
  3. cocoapods官方指南:http://guides.cocoapods.org/
  4. cocoapods基本使用教程:http://code4app.com/article/cocoapods-install-usage
  5. 讲解CocoaPods理论的文章:http://objccn.io/issue-6-4/
  6. 朋友一篇关于cocoapods基础的文章:http://nonstriater.github.io/%E5%B7%A5%E5%85%B7/2014/01/22/ios-bao-yi-lai-guan-li-gong-ju-cocoapods--shi-yong/
  7. 唐巧关于一些cocoapods使用技巧的文章,非常实用:http://nonstriater.github.io/%E5%B7%A5%E5%85%B7/2014/01/22/ios-bao-yi-lai-guan-li-gong-ju-cocoapods--shi-yong/
  8. http://limboy.me/ios/2014/11/28/facebook-app-headers.html,分析Facebook头文件的一篇文章
  9. http://www.tudou.com/programs/view/oTp99lXO29k,facebook工程师在scale上面的演讲
  10. http://yulingtianxia.com/blog/2014/05/26/publish-your-pods-on-cocoapods-with-trunk/,把pod发布到公有仓库的方法

你可能感兴趣的:(基于Cocoapods单工程多应用架构总结)