写点什么

CocoaPods 组件平滑二进制化解决方案

2016 年 7 月 24 日

什么是组件二进制化?

在 iOS 开发中,事实标准是我们使用 CocoaPods 生成、管理和使用 library。这里的 library 就是一个模块、组件或库。二进制化指的是通过编译把组件的源码转换成静态库或动态库,以提高该组件在 App 项目中的编译速度。

我们的方案是转换成静态库,也就是.a 格式的文件加上暴露出来的头文件。

为什么我们需要二进制化呢?

在我们 App 开发中,我们逐渐的抽象了很多模块、业务、UI 等把他转换成私有 CocoaPod 库。其中有一个是用 C++ 和 Objective-C 混写的,源码格式为.mm。在 app 项目编译时.mm 部分代码编译非常慢。这作为一个契机让我们去考虑如何加快编译速度。

这个混写的 CocoaPod 库叫做 YTXChart,之后会以此库为例反复提到。

另外随着业务的扩展,私有 CocoaPod 库和第三方 CocoaPod 库越来越多,App 项目中的文件也越来越多。每次 pod install 安装新库或 pod update 更新库的时候,重新编译的过程需要等待很长时间。这也向我们提出了加快编译速度的需求。

另外如果想要做组件化的话,一定要做二进制化。

所以我们想到了二进制化的方案来解决这个问题,并且很多大公司也是这么做的。

这带来一个新问题:一步就位还是平滑过度

对我们来说,这是一个尝试,不可能开始就决定把所有的私有 CocoaPod 库二进制化,也不可能决定把所有第三方 CocoaPod 库二进制化。当务之急的情况是加快 YTXChart 库编译速度。所以必须找到一个方案平滑过度。

我们的 App 中的 podflie 是这样的

(点击放大图像)

(点击放大图像)

平滑二进制方案需求点

  • 其他的 CocoaPod 库都还是源码。YTXChart 为二进制化。
  • 以后能够逐步迭代把更多的以 YTX 开头的 CocoaPod 库进行二进制化,而不影响主 App。
  • 能够提供一种方式把二进制化 CocoaPod 库切换回源码 CocoaPod 库以便调试。尽量做的方便。
  • 解决 YTXChart 引用依赖的问题。(YTXChart 还依赖了第三方 AFNetworking 和私有 YTXServerId。保证生成的静态库中不会含有 AFNetworking 的内容和 YTXServerId 的内容并且能够编译通过)
  • 利用原来的 YTXChart.git,不创建新项目,不创建新的 git 库。因为我们的二进制化库的生成还是来自于源码,当源码更新时,我们需要一种非常快捷的方式去生成二进制的东西,不希望 copy 源码到某处,或者增加一个 git submodule。
  • 希望 App 源码和 YTXChart 中的源码尽量少或者没有改动。
  • 希望 App 中的 Podfile 尽量少或者没有改动。
  • 希望 Podfile 中的版本号保持风格一致,不会出现’~> 2.2.1.binary’这种情况。
  • 用原来的那一个 CocoaPods Repo Spec。

以下这个解决方案的教程满足了以上所有需求点

注意,以下的例子基于 Cocoapods@1.0.1,而且目前只能是 1.0.1

第一步:源码生成静态库

如果你是通过命令 pod lipo create 创建的 CocoaPod 库并且 pod install 的话,它的目录结构应该像这样子(只列出重要的):

复制代码
YTXChart
|-Example
|-YTXChart
|-Pods
|-YTXChart.xcodeproj
|-YTXChart.xcworkspace
|-Podfile
\-Podfile.lock
|-Pod
|-Assets
\-Classes
\-YTXChart.podspec

在 xcode 中创建新 Target:

复制代码
YTXChartBinaryFile->New->Target->Framework & Library->Cocoa Touch Static Library

如果你们的项目最低支持到 iOS8 可以创建 Dynamic Framework

注意在 Podfile 中加入以下这段

复制代码
 

target ‘YTXChartBinary’ doend

然后 pod install

解释:Cocoapods@1.0.1 会在 Header Search Path 自动加入内容。如果你用 CocoaPods@0.39.0 则需要自己加 Header Search Path 保证依赖库 YTXServerId 和 AFNetwork 能够被找到。如图:

(点击放大图像)

然后把Pod/Classes 中的源码拖入到YTXChartBinary 中,这样选择(这样会link 源码而不是复制):

(点击放大图像)

然后变成这样子:

(点击放大图像)

Headers 需要自己加,里面是你需要暴露的头文件

在 YTXChartBinary Target 中的 Build Settings 下找到 iOS Deployment Target 选择和 YTXChart.podspec 中的 s.platform 保持一致。这里是 7.0:

复制代码
YTXChartBinary Target->Build Settings->iOS Deployment Target

在根目录创建 shell 脚本 buildbinary.sh

你也可以创建一个 Aggregate Target 用来执行 shell 脚本

代码如下:

(点击放大图像)

这个脚本写的并不是很好。说说主要做了什么。 Release 不同的静态库,真机和模拟器的。只构建 x86_64,不构建 i386 加快速度

复制代码
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}"

* 通过 lipo 命令合并。新.a 使用 project name 是因为要和 App 项目的 OTHER_LDFLAGS 兼容 -l"YTXChart"

复制代码
lipo -create "${DEVICE_DIR}" "${SIMULATOR_DIR}"
-output "${INSTALL_LIB_DIR}/lib${PROJECT_NAME}.a"

结果:

(点击放大图像)

为什么要删除 i386

实际上,二进制化方案就是以空间换时间。我们这个 YTXChart 库生成的.a 去除 i386 之后大小有 166.3M。上传到 git 仓库后,git 会压缩。增加了 33M 左右。而作为二进制文件,git 是没法做增量的。所以每次上传.a 都会大大增加 git 库大小,增加硬盘使用量。考虑到我们的服务器硬盘只有 60 个 G 能用,以后还会二进制化很多组件。

所以得出:尽量压缩二进制文件大小;尽量不上传.a,直到发布某个版本时才上传。

当然,如果你的服务器硬盘是 1T 的话,我觉得你也可以随便搞。

现在文件目录是这样子的:

复制代码
YTXChart
|-Example
|-YTXChart
|-Pods
|-YTXChart.xcodeproj
|-YTXChart.xcworkspace
|-YTXChartBinary // 空的
|-Podfile
\-Podfile.lock
|-Pod
|-Assets
|-Classes // 里面是源码
\-Products
|-include
|-xxx.h
|-...
\-xxx.h
\-lib
\- libYTXChartBinary.a
\-YTXChart.podspec

第二步:测试生成的静态库

修改 YTXChart.podspec 如下:

(点击放大图像)

注意s.sourcefiles 和s.publicheaderfiles 和s.ios.vendoredlibraries 的路径

Exampl/Podfile 是长这样子的:

(点击放大图像)

执行pod install 后应该是这样子的,然后跑起来没问题

(点击放大图像)

执行pod lib lint --sources=' http://gitlab.baidao.com/ios/ytx-pod-specs.git,master’--verbose --use-libraries --fail-fast 也是好的

至此我们构建出一个静态库,只包含 YTXChart 的内容,不包含依赖 AFNetwork 和 YTXServerId 的内容

证明:把 s.dependency ‘AFNetworking’, '~> 2.0’去除再执行 pod lib lint ‘ http://gitlab.baidao.com/ios/ytx-pod-specs.git,master ’ --verbose --use-libraries --fail-fast 会报出找不到 AFNetwork 相关文件。

题外话:因为 CocoaPods1.0.1 不支持 C++ 项目的 lint(这是一个 defect ),所以这个时候我会切回 CocoaPods@0.39.0 来 lint 和 publish。而前面 pod instal 增加 Search Path 是依靠 CocoaPods@1.0.1。如果你不是.mm 混写的,是不会有这个问题的。尽管使用 CocoaPods@1.0.1。强行当作没看到这个题外话。

下一步解决如何在源码和二进制中切换 修改 YTXChart.podspec 为以下内容:

(点击放大图像)

注意这段if ENV[‘IS_SOURCE’]。我们的需求是优先使用二进制,偶尔才会切回源码。

删除Example/Pods 目录。

执行IS_SOURCE=1 pod install。你会看到Example/Pods/YTXChart/ 里面都是源码

输出Notice:YTXChart Now is source

进一步跑起模拟器,因为是源码编译用了很长时间,模拟器起来,一切也是好的

再试下pod cache clean --all && IS_SOURCE=1 pod lib lint 也是好的

再试下pod cache clean --all && pod lib lint 也是好的

现在我们通过if else 简单地实现了本地Example App 项目切换源码和二进制。

发布到自己的pod repo spec

发布就和正常发布没有任何区别。

检查从spec repo 的source 中安装

Podfile 修改为 pod ‘YTXChart’, ‘~> 0.17.7’

以下两步很重要

pod cache clean --all

删除 Example/Pods

然后 pod install

检查 Example/Pods/YTXServerId/ 和 Example/Pods/AFNetwork/ 发现都是.h .m 源码。

检查 Example/Pods/YTXChart/ 里的是二进制.a 和头文件。跑起 App 并没有问题。

尝试切回源码

如果你直接 IS_SOURCE=1 pod install 你会发现 Example/Pods/YTXChart/ 里的内容都变成了空。

这是为什么呢,因为 pod cache 了一个 podspec.json。可以通过 pod cache list 查看。他 cache 了一个描述如何从 s.source 中找到相关文件。现在的描述还是从 Pod/Products/ 下去找,自然为空。

为了避免这个问题,所以必须执行上面两步。这个是唯一的问题,目前我还找不到更好的解决方案。切换的行为只是偶尔发生,这是可以接受的。

执行 2 步。再次 IS_SOURCE=1 pod install 你就发现 Example/Pods/YTXChart/ 里的内容都变成了.h .mm 源码。跑起 App 也是好的。

为什么 lint 之前要 cache clean。原理是一样的。如果 YTXChart 依赖的 YTXServerId 也被做成了二进制化就需要 cache clean。不过你也可以这样 pod cache clean YTXServerId

特别注意 IS_SOURCE 应当作为一个所有非二进制化 Pod 库的统一标识,并且通知你们的项目组里所有成员。pod install 可能会有某几个已经二进制化的库使用二进制的内容。IS_SOURCE=1 pod install 时,所有的库都将会是源码的内容。

版本管理

请参考这篇我的文章 CocoaPod 版本规范:

http://www.yiqixiabigao.com/2016/07/14/yin-tian-xia-cocoapodshi-yong-gui-fan/

完整分析

当你发布完成之后,查看。我们发现在 Spec Repo 中对应版本的 podspec 就是我们的 YTXChart/podspec。 CocoaPod 从 s.sourcegit 地址和 tag 下载对应的代码,Pod/Products 和 Pod/Classes 里的内容都存在 当你使用 IS_SOURCE=1 时 ENV[‘IS_SOURCE’] 会为 true。CocoaPods 通过 s.source_files 从下载代码的路径找到源码构建 Example/Pods 和 YTXChart.xcworkspace

(点击放大图像)

明白了上面的过程,来再分析下为什么要在切换源码和二进制化时删除cache 和Pods 目录。放几张图就明白了

(点击放大图像)

(点击放大图像)

删除cache 和Pods 目录。IS_SOURCE=1 pod install 观察json。

(点击放大图像)

总结:

  • 没有使用 submodule 或新的 git 仓库来构建出一个不包含依赖内容的静态库。一份原来的 git 仓库。
  • 没有因为构建二进制库而需要增加冗余的源代码。所以当你修改 Pod/Classes 中的源码,可以方便简单地执行 buildbinary.sh 脚本来构建出静态库。一份源码。
  • 共用了一份 YTXChart.podspec
  • 没有大量修改 YTXChart.podspec
  • 使用 pod lib lint 和 IS_SOURCE=1 pod lib lint 检查通过。
  • 没有修改 Podfile。这个 Example 的 Podflie 只是测试需要才改的。主 App 项目中的 Podfile 可以一行都不改。不会出现’~> 2.2.1.binary’。
  • App 中的源码不会因为使用了二进制 CocoaPods 组件而做任何修改。
  • 没有手动配置 Search Path,这样更容易。
  • 在 Example App 中可以通过 IS_SOURCE 灵活地切换源码和二进制静态库。唯一一个问题每次切换要删除 Pods 目录和 pod cache clean --all
  • 跑起 Example App 总是好的。
  • 没有影响到其他库,我可以逐步平滑地把 YTXXXX 一个一个做成二进制。

下一步目标

逐步平滑地把 YTXXXX 一个一个做成二进制;

进一步的把第三方如 AFNetwork 在私有 spec repo 中做份镜像也提供二进制化;

把 Podfile 中绝大部分组件都做成二进制(RN 这种本地安装模式和有 sub spec 的库目前不打算二进制化)。

关于资源文件,资源文件在二进制化中的配置是一样的。

另外,使用二进制化的 CocoaPods 库不会增加 ipa 的大小。所以我们应当优先用二进制化的东西,这可以加快 Archive 速度。

关于有 sub spec 的 CocoaPods 组件

两个方案:

  • 提供一个全集
  • 对每一个 sub spec 都做份二进制并保持它们之间依赖的相互关系

走过的弯路!!!

现在这个解决方案看起来简单,但在当初的探索过程中并不是那么顺利。以下是不成功的尝试!

创建另一个 YTXChartBinary.podspec

  • 把生成的 Products 目录放到 YTXChartBinary 下
  • 把 YTXChartBinary.podspec 目录放到 YTXChartBinary 下
  • Podfile 中通过增加 Binary 字段安装二进制化如 pod ‘YTXChartBinary’, ‘~> 0.17.7’

问题

  • 要维护 2 个 podspec。版本号很可能不统一。
  • 当 pod spec lint 报错:找不到相关文件。
  • Podfile 中通过增加 Binary 字段来切换,非常不方便。
  • 要改 App 源码。当安装二进制的时候 <YTXChart/YTXChart.h> 需要改成 <YTXChartBinary/YTXChart.h>。来回切换都需要改,极不方便。
  • 要改 App 源码。这次一劳永逸。直接这样使用"YTXChart.h"。但这样也不好。

创建另一个专门放二进制化的 Spec Repo,通过不同的 Source 来区分

解决了要改 App 源码的问题。只需要在 Podfile 中加个 source。

不同的 source 例子:

source ‘ http://gitlab.baidao.com/ios/ytx-binary-pod-specs.git
source ‘ http://gitlab.baidao.com/ios/ytx-pod-specs.git

问题

  • 发布两次,lint 两次。
  • 创建了 2 个 Spec Repo。

感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 7 月 24 日 17:505952

评论

发布
暂无评论
发现更多内容

第十二周 作业

熊桂平

极客大学架构师训练营

架构1期 第十二周作业

haha

Learun FrameWork,.Net Core3.1工作流引擎平台

力软.net/java开发平台

.net core 工作流引擎

如何降低微服务测试成本?我的经验之谈

阿里巴巴中间件

智天下APP系统开发|智天下软件开发

开發I852946OIIO

系统开发

LeetCode题解:433. 最小基因变化,BFS,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

TRONex波场智能合约系统开发详解丨TRONex波场链系统开发(源码)

系统开发咨询1357O98O718

系统开发 TRONex波场智能合约 APP开发

年终盘点 | 七年零故障支撑双11的消息中间件 RocketMQ,怎么做到的?

阿里巴巴中间件

消息中间件 双十一

智慧公安大数据分析研判系统开发,合成作战平台建设

WX13823153201

COMP矿池矿机系统开发案例分析

系统开发咨询1357O98O718

COMP矿池矿机系统开发介绍

有道逻辑英语-时态新发现笔记

Leo

学习 前端进阶训练营 笔记 时态

与前端训练营的日子 --Week07

SamGe

学习

第十二周 架构方法学习总结 —— 数据应用

兵长

「奇淫技巧」如何写最少的代码

Kerwin

Java 代码设计 代码技巧

架构师训练营 12 周作业

郎哲158

三金本体挖矿模式系统开发丨三金本体平台源码设计

系统开发咨询1357O98O718

三金本体挖矿模式源码

BMEX交易所系统软件开发|BMEX交易所APP开发

开發I852946OIIO

系统开发

Week 12 作業

Christy LAW

使用Angular8和百度地图api开发《旅游清单》

徐小夕

Java angular.js 前端 angular

第12周作业

饭桶

Week 12 學習總結

Christy LAW

减肥为什么会失败,有可能是因为你仍然在摄入容易消化的食用糖。

叶小鍵

科普 减肥、廋身 盖里·陶比斯 加工食用糖

架构师训练营 12 周笔记

郎哲158

区块链钱包系统开发方案丨多币种钱包系统开发详情

系统开发咨询1357O98O718

区块链钱包开发

第12周总结

饭桶

Forsage系统开发(模式分析)

系统开发咨询1357O98O718

Forsage系统开发案例介绍

Java并发编程:多线程并发内存模型

码农架构

Java并发

vivo 全球商城:从 0 到 1 代销业务的融合之路

vivo互联网技术

架构 分布式 商城项目 商城

ETH场外交易系统开发流程丨ETH场外交易开发源码案例

系统开发咨询1357O98O718

ETH场外交易系统开发

OKO疯矿链系统开发案例(源码)

系统开发咨询1357O98O718

OKO疯矿链系统开发

5分钟完成业务实时监控系统搭建,是一种什么样的体验?

阿里巴巴中间件

体验 监控

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

CocoaPods组件平滑二进制化解决方案-InfoQ