emmmmm,打开博客、社区很多都是说组件化。。。所以现在参考着各路大神的博客&思路自己也跟着实现一下。
什么是组件化?
对于什么是组件化,我的概念其实是模糊的,不敢说大话,怕误人子弟,只能按照自己的理解说说:组件化跟模块化有着本质的相似,模块化又是什么呢?举个例子,例如微信,有微信
、通讯录
、发现
、我
四个业务大模块。那么模块化就是根据业务的不同一个一个来划分模块。每个程序员单独开发一个模块,其他模块不需要他管理维护甚至开发,提供个接口就可以了,这样就简单实现模块化了。模块也有大模块和小模块之分,不仅仅表现于大模块,每一个view或者每一个功能都是模块。
组件化的思想也是一样的,组件化也是根据业务或者功能来划分的,例如:工程上的请求库,如果使用是AFN,必然要封装多几层吧,这样既解耦又容易抽取第三方库。把封装的代码与第三方库集成一起作为请求网络数据的组件。上述例子是按照功能划分的,也可以往大的方向划分,例如苹果公司:Mac是一个组件、iPhone是一个组件、iPod是一个组件等等。
为什么要组件化?
- 提高开发效率,快速编译
- 功能业务划分清晰
- 有效地针对性测试
- 复用性好
想想,工程业务增多、开发人员增多,项目变得冗肿庞大,单单拿编译来讲,编译运行极其的费时,有没有想过仅仅是调整一下view的背景颜色,都需要编译整个项目?有没有想过开发的模块其实跟其他模块一点都没关系的,而我又需要编译其他模块?所以实现组件化还是必要的,这样即避免上述问题又有效解决代码合并的冲突。
这里可能会有一个疑问?组件化是根据业务功能划分的,还是在工程内还是需要编译的啊,怎么提高编译速度了呢?CocoaPods的相关好处就体现了,这篇文章就是基于CocoaPods来实现组件化的,使用过CocoaPods的都知道,项目需要引用哪些库,只要pod install
相关的库就好了。因为是静态库,也不要重复编译,所以提高编译速度。
如何使用CocoaPods来组件化?
组件化分为:
- 私有的(一般是公司内部自己构建的库,不对外开放)
- 公开的(开源库,像GitHub上的都是公开的库)
文章围绕这两个方向来编写,私有库是基于码云,开源库是基于GitHub。
私有库组件化
创建索引库
为什么要创建索引库?作用是做索引,为真正代码库的做索引的。私有库使用国内的代码管理平台:码云
新建索引项目
把索引库添加到本地CocoaPods仓库
查看本地的CocoaPods仓库
- 可以使用文档进入该路径
1 | ~/.cocoapods/repos |
本地的CocoaPods仓库,如下图:
- 可以在终端使用命令:
1 | pod repo |
本地的CocoaPods仓库,如下图:
添加远程索引库
把刚才创建的索引库添加到CocoaPods仓库中,在terminal执行命令:
1 | // pod repo add 索引库名称 索引库地址 |
期间会让你输入账号密码,是你的码云账号和密码,依次输入回车即可。
可以通过上述查看索引库是否被添加到本地的CocoaPods仓库中,如果看到命令完成或者CocoaPods仓库中有,就证明是添加成功了。到这里,先把索引库放在一边,开始真正的组件库,也就是存放你要存放代码的库中。
创建私有的组件库
过程是创建索引库一样的,如图显示:
创建模板库
首先在在你要存放这个私有库的地方新建一个文件夹,例如我在桌面上新建一个文件夹名称叫:DDCategory。这个名字你可以自定义,不一定是根据库名来,当然按照组件库的名称最好,方便管理。
使用命令进入刚创建的文件夹中:
1 | cd /Users/fty/Desktop/DDCategory |
然后在terminal执行命令:
1 | // pod lib create 组件名 |
上述配置可自由选择,执行命令后会自动打开工程,如下图所示:
删除ReplaceMe文件
添加内容到模板库上
进入组件库的路径:
添加内容:
修改Spec文件
在DDCategory.podspec文件中修改一下内容
1 | s.summary = 'DDCategory是分类工具组件' |
为什么要修改呢,因为生成的可能跟你本地CocoaPods保存的账号绑定了,默认使用,例如我是GitHub的账号和地址,所以要改成对应的。
上传组件代码到码云上
提交代码
cd进入DDCategroty路径:
执行命令:1
2
3git add .
git commit -m '第一次提交(自定义)'
git remote add origin https://gitee.com/DaisukeIT/DDCategory.git
1 | // 第一次push如果报错的话可以加上-f |
期间有可能出现输入码云的账号密码,依次输入回车即可。
打分支标签
执行命令:
1 | git tag '0.1.0' |
0.1.0要跟DDCategory.podspec文件中的s.version对应。
提交podspec到私有索引库
验证spec文件
cd 进入DDCategory路径中
本地验证
执行命令:
1 | // 本地验证不会验证 s.source 中的tag |
- 远程验证
1 | // 远程验证会验证 s.source中的tag,如果此时没有打上相应的标签则会报错 |
之所以出现警告是因为DDCategory.podspec没有配置好,当然你可以在命令后面加上--allow-warnings
。但是这样不好,上面的配置是已经消除了警告的,按照上面的配置,这个是不会出现的。
提交podspec
执行命令:
1 | // pod repo push 私有索引库名称 spec名称.podspec |
这样就提交成功了,现在去码云上看看是否真的提交成功了
看到这里证明是提交成功了。但是在我实践的过程经常遇到警告:
1 | [!] The spec did not pass validation, due to 2 warnings (but you can use `--allow-warnings` to ignore them). |
警告信息可以完善消除,也可以加上--allow-warnings
来忽略:
1 | pod repo push DDCategorySpecs DDCategory.podspec --allow-warnings |
使用自己的组件库
创建工程&创建Profile文件
- 创建工程
- 创建Profile文件
进入工程路径:
1 | cd /Users/fty/Desktop/Test |
然后执行命令:
1 | pod init |
OMG,出现错误了。。。
1 | [!] Oh no, an error occurred. |
按照提示找到对应的issues,基本尝试过了,只有这个方法解决的上述问题:https://github.com/CocoaPods/CocoaPods/issues/7697
这样我pod init就成功了。
编辑Podfile文件
1 | source 'https://github.com/CocoaPods/Specs.git' |
其中:
1 | // 第一行是为了保证公有库的正常使用 source |
pod install
可能出现错误:
Test.xcodeproj Couldn’t load Test.xcodeproj because it is already opened from another project or workspace
退出xcode,重新打开工程即可。
到这里就完成了私有库的组件化。
更新远程私有库
作为一个好的组件,不可能一劳永逸,需要不断地修复优化。当业务增多或者功能拓展。也需要不断地进行升级维护。
- cd进入本地库DDCategory路径中
- 添加代码
这里以添加时间分类为例,添加一个时间分类到仓库中,如图所示:
- 提交代码
在terminal执行以下命令:1
2
3git add .
git commit -m '更新描述'
git push origin master
版本更新
版本更新是作为当前仓库操作的一个标志,之前是0.1.0版本,只有NSString分类,现在要更新一个新的版本,作为NSDate、NSString两个分类的版本。当有人不需要NSDate分类这部分的代码,只需要指引到0.1.0版本即可。所以更新版本很重要。
执行命令:
1 | git tag -a '新版本号' -m '注释' |
通过查看码云上的代码库看到有两个标签,说明更新完成:
修改Sepc文件
- 修改podspec文件
打开DDCategory.podspec文件,修改版本号(版本号要与刚才添加的标签一致):
1 | s.version = '0.1.1' |
- 验证远程spec
前提一定要保证你的你的配置文件和代码正确,相关的库引用好,不好就GG了。我就经常碰到这种错误
1 | [!] The spec did not pass validation, due to 3 errors and 2 warnings. |
上面也出现这样的情况是因为存在警告而没有错误,可以忽略,但是现在的情况是出现error,是要解决相关的错误的。在我实践的过程大部分修复了相关的错误,甚至把代码都删除了,还是报同样错误,我的天啊,我都把代码删除了,你还不给我验证通过。于是各种百度、Google都是说解决相关的错误。直到找到说是重新发布release版本,OK,于是我重新提交代码,重新提交新的标签、重新验证,一路绿灯。所以要是你有什么错误解决了还是不能验证通过,那就:重新提交代码 -> 重新提交新的标签 -> 重新验证
1 | pod spec lint |
验证成功。
更新索引库
提交podspec文件执行命令:
1 | // pod repo push 私有索引库名称 spec名称.podspec |
你会看到是0.1.3版本,因为之前没有注意到代码错误,就去验证以至于一直出错(上面的错误),所以重新提交新的版本,然后重新验证。
更新Example的使用
cd进入Example项目路径执行命令:
1 | // --no-repo-update 不更新本地索引库 |
当然也可以使用:
1 | pod install |
依赖第三方库
在很多情况下,会引用到第三方的库,比如:一个请求组件需要依赖AFNetworking库。需要在spec文件中设置相关参数。
例如依赖AFNetworking:
1 | s.dependency 'AFNetworking', '~> 2.3' |
接着重复以上操作:提交代码 -> 远程验证 -> 打标签 -> 更新索引库
如图所示AFNetworking存在一些警告,使用--allow-warnings
忽略。
Subspecs
什么是Subspecs?就是一个库中下面有N个子库,有时候我们只需要用到其中一个子库,而不需要把全部都要pod进来,这样不仅可以减少应用的体积,加快运行速度。
首先我们先用命令看看AFNetworking有什么Subspecs子库:
1 | pod search 'AFNetworking' |
从图上可以看到有5个Subspecs库,那么工程上只用到Reachability这个Subspecs,所以我们只需要在Podfile文件中这样写:
1 | pod 'AFNetworking' 改为 pod 'AFNetworking/Reachability' |
就可以单独使用Reachability功能。那么别人的库支持操作,是否我们也可以创建自己的Subspecs呢?
在创建Subspecs之前,因为例子需要,这里添加一个文件为Tool文件夹,存放工具类。如图所示:
- Subspecs格式
1 | s.subspec '子库名称' do |别名| |
因为需要分离出子库,需要在子库里分别指定,所以直接把原来的s.source_files和s.dependency都注释掉,然后这样写:
1 | # s.source_files = 'DDCategory/Classes/**/*' |
- 远程验证spec
验证的时候发现出现了一个错误:
1 | - ERROR | [DDCategory/Tool,DDCategory/CategoryTool] file patterns: The `source_files` pattern did not match any file. |
说找不到找到匹配的文件,是路径错误了。然后我就仔细分析一下,名称有没有写错,结果是写错了😯😯,好吧修正回来再次验证还是同样的错误,什么鬼?因为没有打标签,就算你改了podspec文件,服务器也会认为是上一个版本,所以发布新的版本,打个0.1.4版本,验证通过😆😆。
- 查看构建Subspecs是否成功
1 | pod search 'DDCategory' |
很遗憾没有像AFNetworking那样可以看到相关的Subspecs,是不是没有成功呢?是不是因为是私有库而看不到呢?
测试:
在Profile文件中修改成:
1 | pod 'DDCategory/Tool' |
然后在终端执行:
1 | pod install |
发现是成功的。所以是因为私有库的原因?不好意思,我也不知道,如果有人知道请告诉我,谢谢。
资源文件处理
添加资源文件
在创建模板库的时候发现有两个文件夹:Assets、Classes。在之前也展示过吧代码是存放在Classes中,那么Assets就是存放资源文件的,如图所示添加资源到Assets中:
修改spec文件
1 | s.resource_bundles = { |
这里把.png后缀去掉,因为图片可能是jpg或者其他,去掉表示任何文件类型都可以
本地安装测试
- cd进入Example工程路径,pod intasll
- 显示图片
我在Test工程中pod install
DDCategory组件,在ViewController中添加UIImageView来显示图片资源:
1 | - (void)viewDidLoad |
结果不显示图片,肯定不显示了。因为imageNamed:方法加载路径不对。官方注释着imageNamed:加载的是main bundle中的资源。所以要加载组件库的bundle才行。而组件的bundle路径是在这里:
新建一个DDBundleTool工具类继承NSObject,来获取组件下的bundle,以及bundle下的图片资源,这里可能有人想说为什么不使用NSBundle分类就搞定的,我也尝试过,发现一个问题,因为底层需要self.class,而self.class指向的是NSBundle的库Foundation,并不是DDDCategory库,DDBundleTool的self.class则指向DDCategory库
.h文件:
1 | @interface DDBundleTool : NSObject |
.m文件:
1 | @implementation DDBundleTool |
得到路径后新添加一个UIImage分类(UIImage+Bundle)来获取图片
1 |
|
那么在DDViewController中显示图片:1
2
3
4
5
6
7
8
9
10- (void)viewDidLoad {
[super viewDidLoad];
UIImage *image = [UIImage dd_imageWithName:@"share_88_qq_friend_normal"];
UIImageView *view = [[UIImageView alloc] init];
view.image = image;
view.frame = CGRectMake(100, 100, image.size.width, image.size.height);
[self.view addSubview:view];
}
@end
可能有人会问为什么加载的目录DDCategory.bundle写死,要加载每一个组件的图片,尽可能自己实现加载步骤,毕竟减少依赖性。
注意:记得每次修改组件中的代码,都是需要提交代码 -> 打标签 -> 修改spec对应的版本 -> 验证 -> 绑定spec这几个步骤的。测试工程中使用重新 pod install 对应的版本。
加载xib
添加xib
如果出现:Safe Area Layout Guide before iOS 9.0错误,修改xib这里:
本地加载xib测试
1 | - (void)viewDidLoad { |
打印错误信息是:
1 | Could not load the "share_88_qq_friend_normal.png" image referenced from a nib in the bundle with identifier "org.cocoapods.DDCategory" |
发现图片在xib中引用的路径不对,所以修改路径:要在图片名称前加上DDCategory.bundle/
再次运行
修改spec文件
在pod中,xib不能当成源文件(即s.source_files),因为xib文件和bundle文件一样被视为资源文件,否则pod install之后会报错”Unable to run command ‘StripNIB xxx.nib’ - this target might include its own product”.所以必须要将xib放入资源文件中(即s.resources)
使用此方式后,虽然可以在不改变原xib任何代码的情况下直接使用,但是需要将xib中使用到的图片文件拷贝到当前项目的Assets.xcassets中
下图就是亲身经历:
因为新建的xib是放在Classes目录下的,所以在podSpec文件中添加:
1 | s.resources = 'DDCategory/Classes/**/*.xib' |
提交代码&打标签
提交代码:
1 | git add . |
打标签:
1 | git tag -a '新版本号' -m '注释' |
再次提醒:版本号要与spec的版本号对应。
远程验证
1 | pod spec lint |
如果验证中出现以下错误:1
ERROR | [iOS] xcodebuild: Returned an unsuccessful exit code. You can use `--verbose` for more information.
根据提示找到真正的错误:
1 | - NOTE | xcodebuild: DDCategory/DDCategory/Classes/TestView.xib:vUN-kp-3ea: error: Safe Area Layout Guide before iOS 9.0 |
因为Safe Area Layout Guide before的原因
所以在xib中把Safe Area Layout Guide before iOS 9.0取消即可。
安装测试
cd到Test工程,重新pod install,在ViewController中
1 | - (void)viewDidLoad { |
补充subspec目录分层
上面说到subspec来实现目录分层,并没有很好地分类归纳,可以看看AFNetworking是怎样分层目录的:
现在整理podspec文件:
1 |
|
最后目录变成:
再次提醒:出现”Unable to run command ‘StripNIB xxx.nib’ - this target might include its own product”是因为找不到xib路径运行,所以要在podspec文件中指定资源路径。
use_frameworks!
不使用use_frameworks!
使用use_frameworks!
区别
- 静态库:(静态链接库)(.a)在编译时会将库copy一份到目标程序中,编译完成之后,目标程序不依赖外部的库,也可以运行。缺点是会使应用程序变大
- framework:实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
具体可参考:podfile中 use_frameworks! 和 #use_frameworks!区别
公开库组件化(Trunk方式)
Trunk注册
出于安全性等方面的考虑,CocoaPods团队把提交代码的方式改为trunk。
如果没有安装cocoapods请在终端执行以下命令:
1 | sudo gem install cocoapods |
注册trunk
1 | // feng.daisuke@gmail.com是GitHub账号 |
–verbose参数是为了便于输出注册过程中的调试信息。执行上面的语句后,邮箱将会收到一封带有验证链接的邮件,如果没有请去垃圾箱找找,有可能被屏蔽了。点击邮件的链接就完成了trunk注册流程,因为电脑端没有翻墙我收不到Gmail的邮件,借助QQ邮箱客户端绑定来收取邮件:
到此已经验证完邮箱信息,按照提示返回terminal使用下面的命令可以向trunk服务器查询自己的注册信息:
1 | pod trunk me |
看到以上信息说明已经注册成功。
索引库
因为CocoaPods已经为我们创建好了,在Profile文件中,你经常看到这个:
1 | source 'https://github.com/CocoaPods/Specs.git' |
没错,GitHub上实现CocoaPods都是基于这个就是索引库。
创建GitHub上的代码库
其实跟码云上基本一样
克隆代码到本地
在本地新建一个文件夹命名为DDCategoryGitHub
使用命令
1 | cd 进入DDCategoryGitHub目录 |
使用SourceTree等工具
创建podspec文件
在终端执行命令:
1 | cd 进入DDCategory目录 |
当然你也可以拷贝别人的podspec文件到该目录下。
创建主要代码文件夹&demo
添加代码
为了跟码云的目录上一致在DDCategory下新建Classes目录,也方便管理。
配置podspec文件
1 | # |
提交代码
1 | // 提交到本地缓存区 |
说明已经提交成功。
打标签
操作之前要注意的点是:
- podspec文件中的s.version版本号要跟最新Tag一致
- podspec文件中的s.source仓库地址也不能写错
1 | // 0.1.0版本号一定要对应podspec文件中的s.version版本号 |
验证spec
- 本地验证
1 | pod lib lint |
- 远程验证
1 | pod spec lint |
可在命令后添加--verbose
来查看剧透报错信息。
验证通过,可能有疑惑为什么是0.1.1版本,因为我调整了目录结构,所以要重新打标签。
至于警告信息:
1 | summary: The summary is not meaningful. |
上传podspec文件
操作之前要注意的点是:
- 必须cd 进入到podspec目录下,才能执行这个代码
1 | pod trunk push DDCategory.podspec --allow-warnings |
上面的命令实际做了三件事:
1、验证你的podspec文件是否合法,在trunk方式之前我们已经用了“pod lib lint”以及“pod spec lint”命令进行验证;
2、上传podspec文件到trunk服务器,最终还是会上传到https://github.com/CocoaPods/Specs中;
3、将上传的podspec文件转成json格式文件。
😯😯😯
1 | [!] You (feng.daisuke@gmail.com) are not allowed to push new versions for this pod. The owners of this pod are dongjia_9251@126.com. |
很遗憾这个库重命名了,DDCategory的拥有者是ongjia_9251@126.com。所以我查看了一下GitHub确实是的。
怎么办呢?
- 重新创建新库(建议,因为能跟库名保持一致)
- 修改名称
查询了一下DDDCategory名字可用,所以建议在创建一个新的pods组件时先看看有没有重命名。
经过修改名字,重新走流程下来:
😯😯😯又是别人的。
好吧,那我就DDDDCategory,这里我就不新建库,直接更改podspec文件名称:DDDDCategory.podspec。s.name要跟库名保持一致。
1 | s.name = "DDDDCategory" |
再次上传:
🎉🎉🎉🎉🎉🎉
搜索库名
1 | // pod search 自己仓库 |
发现索引不到,其实已经上传到cocoapods上了,只不过需要重新更新索引文件。怎么更新pod索引文件?原理:
- pod setup成功后会生成~/Library/Caches/CocoaPods/search_index.json文件
- 把search_index.json文件文件删除
- 重新执行pod search,就会重新更新索引
重复上面的操作还是找不到,可能是代码还没审核过吧。
等待…..
podspec文件更新方法
有时你可能会遇到这种情况:执行pod trunk push操作后发现podspec文件的某个地方写错了,想更新一下。对于这种情况,我们可能会先尝试着在把podspec文件push一次。但是如果你的代码版本号没变(podspec里的version自然也没变)就会提示push失败,即使你更改了podspec的其他地方,pod也会认为这两个文件是同一个。 我目前为止找不到trunk的相关update接口,所以只能顺水推舟,更新源代码版本号(如:1.1.1->1.1.2),重新push version tag,然后再执行pod trunk push操作。
如何划分组件?
划分组件没有一个明确的标准,基本都是依靠着业务需求、功能划分等划分。业务上一般来说是根据不同业务线来划分,比如:发现
、个人中心
两个业务线没有必然关系,可以划分两个不同的大组件;功能上一般来说请求库
、工具库
、基础SDK
、地图
、埋点
、加解密
、数据库
、缓存
等划分。粒度性可以跟团队商量结合,粒度可大可小,按实际开发情况来。
参考文档
以上内容是我参考着以下文章来记录学习实际操作的过程,虽然结果也不尽人意,出现莫名其妙的错误,但是谢谢,对我非常有帮助。如有侵权请联系删除!
参考文章: