组件化实践之初体验
前因
- 随着公司业务的发展和细化拆分,各业务端需自主开发,某些业务存在内嵌逻辑
- 公司当前各端的项目存在重复的功能块和逻辑块
- 业务发展的同时开发团队也在扩展,那么如何处理团队和项目发展带来的一些问题
一番调研之后,我们决定使用组件化来解决当前遇到的这些问题
组件化
1.什么是组件/模块化
简单的来讲可以包括下面这两块
- 独立的业务或功能块(细分来讲)
- 多个功能和业务组成的模块(可以称之为大颗粒度的组件)
对应到我们当前公司目前的项目结构可以大概分为这么几块
- 业务组件: 登陆模块、客服服务、支付模块等等
- 基础UI组件:通用控件(和其他一些widget)、多媒体处理、日历管理等等
- 基础功能组件:网络管理、主题管理、定位管理等等
目标
- 模块拆分,实现业务分离,可跨团队开发
- 各端使用同一套基础组件来进行开发,代码整体可控
- 模块和组件可组装和复用,提高开发效率
- 业务隔离和代码解耦
方案
1.对相关的功能模块和业务组件进行拆分,通过framework的形式发布成内部私有库
2.通过cocoapods实现对私有库的引入和版本管理
3.通过fastlane进行持续集成
技术栈
1.静态库和动态库:共享代码的方式
2.CocoaPods:强大的iOS第三方库管理工具
3.FastLane:自动化持续集成工具集
动态库和静态库
1.静态库 Static Library
即.a文件,项目源码所对应的目标文件(.o/.obj)的打包体;配合.h文件使用,暴露.a中的方法或成员
2.动态库 Dynamic Framework
即.framework文件,是一种资源打包方式,可以说是一个bundle文件夹(包含代码文件、头文件、资源文件等)
3. Library VS Framework
-
静态库不能包含xib 、storyboard、图片这样的资源文件,其他开发者必须将它们复制到 app 的 main bundle 中才能使用,维护和更新非常困难
-
而动态库 则可以将资源文件包含在自己的 bundle 中
-
静态库只能随应用二进制文件一起加载,链接时完整地拷贝至可执行文件中,被多次使用就有多份拷贝
-
而动态库链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。(iOS目前只允许使用系统动态库,如系统的UIKit、Foundation等)
总结一下就是
1F301289-415F-476F-B47E-F2E072479C1A.png我们可以看到动态库相比较静态库而言是有很多方面的优势的,然而在iOS8之前苹果是不允许我们使用动态库的,在iOS8之后苹果推出了Embedded Framework的概念,即允许我们使用动态库,我们可以在工程模板内看到Cocoa Touch Framework。苹果之所以在iOS8上推出这个东西其原因是在iOS8时苹果推出了APP Extension (程序扩展,例如我们制作通知栏的扩展程序以及允许我们使用第三方键盘等等),这种情况下需要和我们的主APP进行部分代码共享,还有一个原因就是在iOS8时推出了Swift,由于Swift的语言特性(运行时特性尚未稳定),所以目前只支持动态库。
简单的来讲Cocoa Touch Framework有以下两个特点
- 生命周期被限制在单个APP进程中
- 不同的APP使用到时需重复加载
所以说Cocoa Touch Framework其实是阉割版的动态库,不过这也是人之常情,毕竟苹果如果开放了真正的动态库,那还不天下大乱。
我们简单来看下使用组件化后预期的项目结构
0FCDCF7C-E25E-4DD8-83E6-AA8BF3D953E1.png首先我们目前没有对更多的模块做拆分(后续也会拆分出来),只是把商城这个模块拆分出来(由另一个商城的团队进行开发),然后以Framework的形式加入到我们主APP当中。
如上图所示我们可以看到我们的主APP需要引入我们的商城端,我们的商城端和主APP同时又需要依赖于我们抽出的内部组件;并且这三者都会依赖于一些第三方的Framework来进行开发。我们最终的目标就是把这些所有的东西融合在一个工程里面,也就是我们的主APP中。
最终的方案就是让商城、内部组件、第三方Framework都以Framework的形式引入到我们的主APP中
567E1911-C9F9-4827-98AA-A5AC9E2E407E.png关于如何制作一个Framework网上已有很多相关的资料,可自行Google,这里就不做相应的介绍
那么当制作好我们的Framework后,如何让另一个工程能够使用到我们的Framework,我们最先想到的方法就是把我们的Framework直接导入到工程中,可能还需要添加相应的依赖和其他的一系列配置。那么问题就来了,首先这种方案依赖和配置非常繁琐,更重要的是当我们的Framework更新时我们需要做的是删除上一个版本然后添加新版本,然后可能还需要更新一些依赖配置等等,这种版本管理方式不仅效率低下而且还可能出现不可控的风险。幸运的是在我们的iOS开发中另一种有更好的管理依赖的方式,这就是我们的Cocoapods
Cocoapods
1.什么是Cocoapods
简单的来说Cocoapods是我们 iOS 应用程序开发的一个第三方库依赖的管理工具(使用Ruby开发),通过它我们可以高效率的导入、配置以及管理所用到的第三方,如果你是一个iOSCoder相信肯定对它不会陌生
2.特点/好处
简单的来说可以有以下几个方面的好处
- 避免直接导入文件,方便后续版本管理
- 简化集成流程,避免不需要的配置
- 自动处理库之间的依赖关系
- 简化开发者代码的发布流程
3.原理
我们简单的来讨论下Cocoapods的功能实现原理,在讲原理之前我们首先需要了解下我们Xcode的几个大的项目工程结构
Target:一个target对应一个目标文件,也就是一个APP;target可以相互独立,也可以依赖于另一个target
Project:project是构建一个或者多个APP所需的所有文件、资源和信息的存储库,可包含多个target,可以管理不同的target间的关系,我们默认创建的工程类型就是Project
如上图所示,我们的工程Project下对应了三个target,分别是我们的App和两个单元测试target
Workspace: workspace是最大的集合,可以包含多个Xcode Project,以及要包括的任何其他文件。可以管理多个Project间的关系(引用和依赖)
如上图所示,在这种多target的情况下可能会存在两种依赖关系
1.显式依赖:某个target直接引入了另一个target
2.隐式依赖:某个target也需要依赖于另一target,但没有直接引入
我们Cocoapods的方式就是创建一个Workspace和Pods Project文件,通过Pods Project文件来管理所需要依赖的第三方的Framework,然后把我们的项目工程的Project和创建的Pods Project一起放入创建的Workspace中,最后通过这个Workspace来实现对所有工程的管理
如果你的项目使用了Cocoapods,你会发现有下面几个特点:
1.主工程没有显示依赖各个第三方库,Pods 项目最终会编译成一个名为 libPods.a (Pods_....framework)的文件,主项目只需要依赖这个.a文件即可
5859815B-9F01-47BD-B473-EE1FAAF8BD1A.png FE92E691-30A4-48A0-8503-80998F74169A.png2.对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本在每次项目编译的时候都会执行,将 Pods 依赖库的各种资源文件复制到目标目录中
3.CocoaPods 还通过一个名为 Pods.xcconfig 的文件来在编译时设置所有的依赖和参数
如果什么都不干,我们的主工程肯定是不能够引用Workspace管理的第三库的。如果打开工程的Build Settings,我们可以看到Header Search Paths、Library Search Paths、Framework Search Paths等这些字段,这些字段等于告诉了我们主工程所依赖的第三方库在哪里也就是资源路径。然而知道路径只是第一步,因为我们还没有确立他们之间的引用关系,所以我们得声明一下,告诉编译器他们之间存在着引用关系,这个字段就是Other linker flags,如下图所示
CCCA5B64-B242-438D-9DC0-EA513E807423.png4.如何使用
如果你新建一个工程,在引入Cocoapods进行管理之后,你会发现工程目录下会多出这么一些文件,如下图所示
podfile:说明文件,说明pod需要导入和管理那些依赖库
podfile.lock:用来保存已安装的pod的依赖库的版本
pods文件夹:Pod和依赖库的工程文件夹
我们简单的来看下导入的过程,如下图所示
导入过程首先会根据我们的podfile中的地址找到对应的第三库所在的git仓库,如果有指定tag的话会去定位到对应的tag的提交(没有取最近的一次),然后会去检索工程目录下的podspec文件,通过podspec文件来做一系列的验证,比如工程名,版本号等等,验证合法之后会根据podspec中的source_file字段找到需要下载的代码文件,然后配合对应的一些资源文件和配置文件一起下载下来,共同组成我们所需要的第三方库文件
5.制作podspec
如上文所叙,要想让我们的工程能够支持cocoapods,我们需要制作工程对应的podspec,并将其发布到cocoapods上。
如何制作一个合适的podspec这里就不做叙述,可以通过下面的一些文章来进行了解
1.CocoaPods建立自己的Podspec
2.发布CocoaPods组件碰到的坑与心得体会
Fastlane
Fastlane是用Ruby语言编写的一套自动化工具集和框架,它可以非常快速简单的搭建一个自动化发布服务,并且支持Android,iOS,MacOS。比如在我们的iOS开发过程中,经常会经历从 编译->打包上传->填写应用更新数据->等待iTunesConnect编译->选择版本发布等这一系列过程,而Fastlane则可以帮我们自动化处理这些事情。
具体的可参考:小团队的自动化发布-Fastlane带来的全自动化发布
以及 Fastlane实战(一):移动开发自动化之道
踩到的坑
由于Swift不支持.a静态库的原因,当我们的第三方库内包含.a静态库,组件库内引用了这个第三库,然后我们在引入组件库时就会pod install报错,如下
[!] The 'Pods-LJA_Example' target has transitive dependencies that include static binaries:
(/Users/nero/Desktop/Static_Dynamic/Componment/Example/Pods/libWeChatSDK/libWeChatSDK.a)
具体的解决方案参考这篇文章组件化-动态库实战
未来
-
更细分的功能组件和模块
我们当前只是对部分模块和功能进行了拆分,如只是把商场单独作为一个模块分离出去了,后续可能需要对相应模块和功能进行更细分的拆分 -
模块间的通信,采用何种方式?
在我们的模块和组件进行拆分过后,那么一个需要思考的问题就是这些模块和组件间的通信该如何解决,当前对这方面的讨论也比较多,具体可参考这些文章
iOS组件化思路-大神博客研读和思考
iOS组件化方案
iOS APP组件化开发实践
我们这一块的方案还未确定,后续确定了再做相应的讨论 -
使用jazzy为内部库添文档说明
jazzy是Realm开源的一个使用ruby编写的插件,配合xcode7以后支持的markdown注释功能,可生成类似于苹果官方文档样式的代码文档 Jazzy