欢迎点击上方蓝字“iOSer”关注我们
再点击右上角“...”菜单,选择“设为星标”
我们一起精进、成长!
本文由掘金优秀作者
被帅醒的吴宝宝 授权发表
原文链接 | https://juejin.im/post/5efaf0655188252e42157e8a
2、需要手动删除/Pods/XXX
3、不能针对单独库进行切换,除非自定义白名单之类的规则 |
| tag切换 | 1、可以针对单独某个库进行切换 | 1、需要执行pod update(需等待repo master源的更新)
2、私有库的tag需要打两个,podspec上传时需要传两次
3、切换时需要手动修改Podfile文件的版本信息 |
觉得不错的话,别忘了点个''三连'' 哦~ ?
随着业务的扩展、项目体积的增大,CocoaPods
组件库越来越多,每次重新编译的时候速度越来越慢,这给我们提出了需要提高编译速度的需求。
为了提高项目编译速度,对于大量使用组件化开发的项目组而言,组件二进制化是必然要走的路线,虽然中心思想就是要将各个组件打包成.a
二进制库,但是各个公司可能方案都不太相同,网上的方案也有很多可供选择,这里我大体总结成以下几种:
- 分仓库管理
Carthage
管理podspec
环境变量(宏管理)podspec
分tag
管理(只针对私有库)
实施
1、创建pod私有库
? 如果您对这一块很了解请跳过这一步直接看第二步 对于私有库的创建,一般我们会采用pod lib create XXX
模板来进行构建(如果还不知道这条命令是干嘛的同学可以先移步了解一下
理解CocoaPods的Pod Lib Create
)
这里我们拿
ABC
这个项目进行举例,首先我们执行
pod lib create ABC
创建
ABC
的私有库
CocoaPods
会从
https://github.com/CocoaPods/pod-template.git
下载模板文件,并询问你一些构建信息,正常填就好了。
[MichaeldeMacBook-Pro:~ michaelwu$ pod lib create ABC
Cloning `https://github.com/CocoaPods/pod-template.git` into `ABC`.
Configuring ABC template.------------------------------
To get you started we need to ask a few questions, this should only take a minute.
If this is your first time we recommend running through with the guide: - https://guides.cocoapods.org/making/using-pod-lib-create.html( hold cmd and double click links to open in a browser. )
What platform do you want to use?? [ iOS / macOS ]>
一般如果我们构建好了的话工程目录会类似这样一个结构:
.
├── ABC
│ ├── Assets
│ └── Classes
├── ABC.podspec
├── Example
│ ├── ABC
│ ├── ABC.xcodeproj
│ ├── ABC.xcworkspace
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ └── Tests
├── LICENSE
├── README.md
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
这里你会发现,
CocoaPods
已经帮我们创建好了
Demo
、源文件目录、
Podfile
、
podspec
、
.gitignore
文件等(真是一个贴心的小家伙),而且很规范,
Demo
文件在
Example
目录下
窥视一下
podspec
文件你就明白了源码需要指定在
./Classes/**/*
路径下
s.source_files = 'ABC/Classes/**/*'
为了演示效果,我们创建两个源文件
ABC.h
与
ABC.m
并放入
Classes
路径下,同时将默认的
ReplaceMe.m
删除
接着在
Example
下执行
pod install
,可以发现
ABC.h/m
已经导入成功
至此,我们就明白了私有库的创建过程,需要编写源代码需要放入指定目录下并在执行
pod install
进行同步
2、创建静态库
组件二进制其实指的就是打包成动态库/静态库,由于过多的动态库会导致启动速度减慢得不偿失,此外iOS
对于动态库的表现形式只有
framework
,若想做源码与二进制切换时,引入头文件的地方也不得不进行更改,例如:
import <ABC.h> // 源码引用import <ABCBinary/ABC.h> // 动态库引用
而打包成静态库
.a
文件(注意不要打包成
framework
形式)则不需要更改引用代码,所以综上所述,我们选择打包成静态库的方式不需修改引用代码、缩小体积提升编译速度。
确定目标之后,就是实施了,一般而言我们私有库都会在远程托管地址有
git
仓库,然后再上传到指定的私有源(specs)上,那么就会引申出几个问题:
- 要不要将静态库上传到
git
(如果包体积很大会很占用git
空间) - 怎么做到一套代码同时管理源码和二进制
- 为了能够调试源码,如何在源码及二进制间切换(下一步骤会讲到)
3、静态库与源码如何用同一套代码管理?
其实这个很简单,我们接着拿ABC
这个项目举例子,进入
Example
打开我们的
ABC.xcworkspace
工程,然后创建新的
Target
为静态库,并取名为
ABCBinary
(一定要取这个名字,后面我会解释)
File->New->Target->Static Library
此时在
Example
目录下会增加刚刚创建的
Target
文件夹,结构如下:
├── ABCBinary
│ ├── ABCBinary.h
│ └── ABCBinary.m
Xcode默认会帮我们生成两个文件,我们将
.h
改名为
placeholder.h
,
.m
删除,这里为什么要将
.h
换成
placeholder.h
呢?先卖个关子,待会我们再作解释。
我们把刚才写的
ABC.h/m
的源码拖到
ABCBinary
中,注意不要勾选
Copy items if needed
,只做引用即可
之后我们需要到
ABCBinary
的
Build Setting
中指定静态库所能运行的最低版本:
Build Setting->Deployment->iOS Deployment Target
并在
Build Phases
中指定头文件,将
ABC.h
拖入Public中,具体步骤:
TARGETS->ABCBinary->Build Phases->New Header Phase
至此我们完成了一套代码管理二进制与源码,但有个小细节需要注意:就是如果源代码有变动需要在
XXXBinary
文件中重新导入一遍,不然二进制的文件不会自动更新(同学们有好的建议可以评论区讨论下)
3、是否需要将二进制上传至git?
其实git
对代码管理时会将每一次的
commit
做备份(在
.git
这个文件夹下),以便于随时代码回滚,倘若我们每次都对私有库进行更新时都将二进制包传至
git
,那么时间久了无疑是对
git
仓库空间的一个挑战(如果你们公司空间足够大不需要考虑,那么请忽略这一步)
网上有很多针对这个问题给出的解决方案,但都不是很完美,大体上都是说将
二进制包
单独传到另一份静态资源地址,以此解决
git
过大问题,不过我觉得没有解决痛点,能不能不上传二进制包呢?
结论当然是可以,
CocoaPods
本地的缓存目录在
~/Library/Caches/Cocoapods
其实每次我们更新
pod
库时,
CocoaPods
都会先从指定源去拉源代码再根据该库的
podspec
文件指定输出目标文件,那么我们如果能把静态库打包推迟到
pod install
阶段就不需要上传二进制包到
git
了,但是如何做到延迟打包呢?
很幸运,
CocoaPods
提供了针对
podspec
的预执行脚本,
prepare_command(戳我进官网)
命令,该命令可以指定相应的脚本在
pod install
时去执行,那么我们就可以将编译打包的脚本放入其中,从而完成延迟打包
好了,理论上貌似可行了,实践出真知啊(? 绝对不能做一个理论性选手啊),具体怎么做?
首先我们需要一个能一键打静态库包的脚本(一刀99级那种),帅气的我这边已经为大家准备好了,只修改一下
PROJECT_NAME
即可,拷贝脚本至根目录并赋予执行权限:
# 当前项目名字,需要修改!
PROJECT_NAME='ABC'
# 编译工程
BINARY_NAME="${PROJECT_NAME}Binary"
cd Example
INSTALL_DIR=$PWD/../Pod/Products
rm -fr "${INSTALL_DIR}"
mkdir $INSTALL_DIR
WRK_DIR=build
BUILD_PATH=${WRK_DIR}
DEVICE_INCLUDE_DIR=${BUILD_PATH}/Release-iphoneos/usr/local/include
DEVICE_DIR=${BUILD_PATH}/Release-iphoneos/lib${BINARY_NAME}.a
SIMULATOR_DIR=${BUILD_PATH}/Release-iphonesimulator/lib${BINARY_NAME}.a
RE_OS="Release-iphoneos"
RE_SIMULATOR="Release-iphonesimulator"
xcodebuild -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphoneos clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_OS}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_OS}"
xcodebuild ARCHS=x86_64 ONLY_ACTIVE_ARCH=NO -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphonesimulator clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_SIMULATOR}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_SIMULATOR}"
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -rp "${DEVICE_INCLUDE_DIR}" "${INSTALL_DIR}/"
INSTALL_LIB_DIR=${INSTALL_DIR}/lib
mkdir -p "${INSTALL_LIB_DIR}"
lipo -create "${DEVICE_DIR}" "${SIMULATOR_DIR}" -output "${INSTALL_LIB_DIR}/lib${PROJECT_NAME}.a"
rm -r "${WRK_DIR}"
我们还是拿
ABC
的项目来接着实践,拷贝脚本后,先来看一下我们
ABC
目前的结构:
.
├── ABC
│ ├── Assets
│ └── Classes
├── ABC.podspec
├── Example
│ ├── ABC
│ ├── ABC.xcodeproj
│ ├── ABC.xcworkspace
│ ├── ABCBinary
│ │ └── placeholder.h
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ └── Tests
├── LICENSE
├── README.md
├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
└── build_lib.sh
可以看到最下面多了一个
build_lib.sh
脚本(就是刚刚拷贝的那个脚本),另外
ABCBinary
里面有一个
placeholder.h
,这里解释一下之前埋下的悬念:因为
ABCBinary
文件夹里对于源码的引用没有
copy
,所以在提交到
git
时会自动将文件夹清空(也就是说在git目录里找不到),因此需要加一个占位防止文件夹不上传到
git
,但是切记不要编译到静态库里!
好的,至此一键打包脚本也准备好了,通过查看脚本我们发现这个二进制包最终会输出到根目录下的
./Pod/Products/
目录中,那不还是得传到
git
吗?别急,你忘了
gitignore
了吗?
配置
.gitignore
忽略
Pod/
文件不就行了嘛,在
.gitignore
最下面增加忽略
Pod/
好了至此,我们完成了自动打包脚本及
git
忽略二进制包,再也不用担心我们的
git
仓库空间压力了(运维小哥哥们表示“尼玛松了一口气”)
4、如何在源码与二进制间切换
在提升编译速度的前提下,还需要考虑到能随时进行源码调试,这就涉及到了如何在源码与二进制间切换的问题,网上的思路有很多:环境变量、白名单、tag切换等。 这几种方式在前言部分我们已经讲过了,接下来我们介绍一下“环境变量”和“tag切换”这两种方式:4.1、 如何利用tag进行切换:
首先我们需要约定好规则:当version
中包含
.Binary
关键字时执行
prepare_command
命令并输出
source
为静态库,具体操作如下(
podspec
是用
ruby
写的,支持条件判断):
if s.version.to_s.include?'Binary'
puts '-------------------------------------------------------------------'
puts 'Notice:ABC is binary now'
puts '-------------------------------------------------------------------'
s.prepare_command = '/bin/bash build_lib.sh'
s.source_files = 'Pod/Products/include/**'
s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
s.public_header_files = 'Pod/Products/include/*.h' else
s.source_files = 'ABC/Classes/**/*'end
由于
tag
是根据
version
走的(
tag => s.version.to_s
),因此只需要我们修改
s.version = '0.1.0.Binary'
即可实现二进制打包
好,我们贴一段此时
ABC.podspec
完整的代码:
Pod::Spec.new do |s|
s.name = 'ABC'
s.version = '0.1.0.Binary'
s.summary = 'A short description of ABC.'
s.description = <DESCTODO: Add long description of the pod here.DESC
s.homepage = 'https://github.com/609223770@qq.com/ABC'# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { '609223770@qq.com' => '609223770@qq.com' }
s.source = { :git => 'https://github.com/609223770@qq.com/ABC.git', :tag => s.version.to_s }# s.social_media_url = 'https://twitter.com/'
s.ios.deployment_target = '8.0'if s.version.to_s.include?'Binary'
puts '-------------------------------------------------------------------'
puts 'Notice:ABC is binary now'
puts '-------------------------------------------------------------------'
s.prepare_command = '/bin/bash build_lib.sh'
s.source_files = 'Pod/Products/include/**'
s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
s.public_header_files = 'Pod/Products/include/*.h' else
puts '-------------------------------------------------------------------'
puts 'Notice:ABC is source code now'
puts '-------------------------------------------------------------------'
s.source_files = 'ABC/Classes/**/*'endend
让我们来看看效果,在
Example
下执行
pod install
,发现切换过来了,Nice ?~
接下来验证本地
podspec
(若有问题按照提示更改,
ssh://xxx.git
是你私有源的地址):
pod lib lint --sources=ssh://xxx.git --allow-warnings --verbose --use-libraries
若没问题,在
ABC
的
git
仓库打一个
0.1.0
的版本
tag
,并上传
ABC.podspec
至私有源,上传成功后修改
podspec.version
为
0.1.0.Binary
再次执行上传:
pod repo push XXXSpecs ABC.podspec --allow-warnings --verbose --use-libraries
✅ 如果一切顺利,我们已经将Binary和源码的
ABC
上传到了私有源。
接下来我们在实际项目实验一下,
Podfile
中指定,并执行安装
pod 'ABC', '~> 0.1.0' # source code
pod install
不出意外源码
ABC
安装成功,这时我们修改
tag
版本后面加
.Binary
,再次执行
pod install
,如下所示:
pod 'ABC', '~> 0.1.0.Binary' # source code
pod install
很遗憾,你可能会发现源码并没有切换成功,为什么呢?
原来
Pod
的版本管理是放在
Podfile.lock
中,每次执行
pod install
时若
Podfile.lock
中已经存在此库,则只下载
Podfile.lock
文件中指定的版本进行安装,否则去搜索这个
pod
库在
Podfile
文件中指定的版本来安装。
因此,解决办法有两种,一种是从
Podfile.lock
中将包含
ABC
的地方全部删除或是干脆直接删除
Podfile.lock
,再次执行
pod install
会发现切换变过来了。
还有一种方法是执行
pod update
,这也是 update 和 install 的区别,update会读取
Podfile
中的版本去更新
Podfile.lock
文件。(
戳我查看pod install和pod update区别
)
pod update ABC
执行后,先是会更新一下master和其他私有源,再去更新
ABC
,发现此时切换成功。(缺点就是如果
Podfile
中如果某些库没有指定版本就会更新到最新版本)
4.2、如何利用Ruby环境变量进行切换:
Ruby语法支持一些环境变量的读取,因此可以在pod install
时增加参数以此判断是否要切换源码:
IS_SOURCE=1 pod install # 1 代表源码
IS_SOURCE=0 pod install # 0 代表二进制
pod install # 默认也是二进制
在
podspec
中做修改:
Pod::Spec.new do |s|
s.name = 'ABC'
s.version = '0.1.0.Binary'
s.summary = 'A short description of ABC.'
s.description = <DESCTODO: Add long description of the pod here.DESC
s.homepage = 'https://github.com/609223770@qq.com/ABC'# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { '609223770@qq.com' => '609223770@qq.com' }
s.source = { :git => 'https://github.com/609223770@qq.com/ABC.git', :tag => s.version.to_s }# s.social_media_url = 'https://twitter.com/'
s.ios.deployment_target = '8.0'if ENV['IS_SOURCE'] #s.version.to_s.include?'Binary'
puts '-------------------------------------------------------------------'
puts 'Notice:ABC is source code now'
puts '-------------------------------------------------------------------'
s.source_files = 'ABC/Classes/**/*'else
puts '-------------------------------------------------------------------'
puts 'Notice:ABC is binary now'
puts '-------------------------------------------------------------------'
s.prepare_command = '/bin/bash build_lib.sh'
s.source_files = 'Pod/Products/include/**'
s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
s.public_header_files = 'Pod/Products/include/*.h'endend
同tag切换一样,这种方式在实际项目中切换也存在问题,需要两个必要步骤:
pod cache clean ABC # 先清理ABC的pod缓存
rm Pods/ABC # 再把ABC从实际项目中的Pods目录下移除
5、对比两种方式
方式 优点 缺点Ruby环境变量切换 | 1、不需要上传两份podspec | |
2、切换时不需要修改Podfile | 1、需要清除私有库的缓存 |
3、不能针对单独库进行切换,除非自定义白名单之类的规则 |
| tag切换 | 1、可以针对单独某个库进行切换 | 1、需要执行pod update(需等待repo master源的更新)
2、私有库的tag需要打两个,podspec上传时需要传两次
3、切换时需要手动修改Podfile文件的版本信息 |
6、总结
好,至此切换tag
方式的组件二进制方案就介绍完了,我们通过
ABC
项目的实践了解了整个过程:
- 创建pod私有库
- 在私有库Demo中创建静态库target,并配置头文件及最低iOS版本支持
- 创建打包脚本
- 设置
.gitignore
忽略输出的二进制包 - 配置podspec根据tag版本判断或根据环境变量判断
- 验证并上传源码及二进制的podspec
- 在实际项目中切换时需要执行
pod update
或删除Podfile.lock
中相关库信息
7、链接
本文demo相关链接如下,另附自动上传podspec脚本地址( 相关文章 ),喜欢的朋友点个star- 组件化方案demo地址:CocoaPodsBinary
- 自动上传podspec脚本:upload_podspec
觉得不错的话,别忘了点个''三连'' 哦~ ?