持续集成 Xcodebuild 编译加速,Cocoapods 二进制化实践

不二家 · January 30, 2018 · Last by 不二家 replied at February 05, 2018 · 3671 hits
本帖已被设为精华帖!

背景

为了加快当前 iOS 项目的编译速度,加速编译被放上日程。从网上看了很多这方面的方案,最后选择了 Pod 二进制化,来加速一些编译速度,原理是工程引用库后在编译阶段就不需要对 Pod 库进行编译。

目标

我们当前的目标是将私有库编译成二进制文件后,但是有一些限制:

  • 尽量做到不侵入我们的工程源码,比如不更改import方式等
  • 推进所有三方库二进制化,包括我们的私有库和github上的三方库
  • 不破坏当前 cocoapods 管理依赖库的原则

基本思路

由于我们不能破坏当前的 Pod 管理方式,需要做的就是将当前项目工程中的源码替换成静态链库,最后编译的时候只需要对工程源码进行编译,不用编译 Pod 库,只需要最后的链接签名,完成打包。

搜索到的方案

最后的方案

需要钱解决的核心问题有:

  • 将所有库打成 Framework
  • 引入方式,当前的引入方式是执行pod install然后下载各种 pod 的依赖,好处是 cocoapods 可以管理所有互相依赖的问题
  • 不能将打好的 Framework 手动拖拽进项目,要以最小的代价完成替换

解决打 Framework 的问题

我们可以使用 xcodebuld,Fastlane 的 gym,或者 cocoapods packager 实现打包,但是这几种方式我都尝试了一下,并未成功,最后发现 Carthage 可以非常方便的实现库变成 Framework。

使用命令carthage build --no-skip-current --platform iOS

我们只需要关注 iOS 平台,故只选择 iOS 平台即可

但是在这里我们需要做一个区分,三方库是否支持 Carthage 化,换句话说就是这个库是否已经进行过 Carthage 化了。

根本的区别是在 share scheme,当这个库已经支持 Carthage 了,比如AFNetworking,AFN 已经支持 Carthage 了,直接下载库后,然后再根目录执行

carthage build --no-skip-current --platform iOS

结果为:

*** xcodebuild output can be found in
/var/folders/my/cl8q6md51nz8vq85cvr5ncc40000gn/T/
carthage-xcodebuild.jfnLwA.log
*** Building scheme "AFNetworking iOS" in AFNetworking.xcodeproj

如果是未进行 Carthaeg 化的呢,如果同样执行的话,结果是:

*** xcodebuild output can be found in
/var/folders/my/cl8q6md51nz8vq85cvr5ncc40000gn/T/carthage-xcodebuild.eBdI
4c.log
*** Skipped building category due to the error:
Dependency "xxx" has no shared framework schemes for any of the platforms: iOS

那么如何做到依然打出 Framework 呢?

我们需要做的流程是:

首先,强调我们的私有库是官方标准格式的私有库

pod lib create xxx

现在我们需要修改的是用

  • 打开 Example 下的对应示例工程的 xxx.xcworkspace,用 xcode 打开
  • 然后选中 Manage Scheme

  • 这个时候重新回到根目录下,执行

    carthage build --no-skip-current --platform iOS
    

    便能方便的生成一个已经二进制化的 Framework,同时里面还有我们需要的headers

解决引入方式的问题

谈到这里,我们需要关注一下 cocoapods 是如何管理依赖库的?

我们都知道 pod install 之后会下载从 Pod Spec 里面拉取源码,然后放到工程目录 Pod 下面进行管理,然后也有些库是直接拉取之后,以 Framework 的方式引入,比如友盟,OpenCV,Fabric 等。

我们先看看友盟的podspec.json是如何实现的?

可以看到友盟提供的依赖下载就是.zip 包,存储的便是 Framework。

我们再可以拿 OpenCV 这个库来看一下:

它的实现显然是不一样了:

  • 先 pod install 来 lone 代码

  • prepare_command 执行 build.py,类似于编译开源项目时常见的./configure

  • prepare_command 结束后,由 Cocoapods 把 framework 插入 Xcode 工程中

我们注意到 Prepare_command 的命令,它是用来干嘛的呢?

CocoaPods prepare_command支持各种脚本的使用,Python Shell Ruby

意味着,如果我们不想改动之前的 pod install 管理依赖包的模式,只需要在 podspec.json 里面增加 prepare_command 脚本,然后执行打包下载,便可以将源码打包成 Framework 无缝引入我们的项目中,然后我们拿三方库 mantle 示例一下,

先 fork mantle 到自己的 github 仓库下,然后更改新建 mantle.podspec.json:

{
  "name": "Mantle",
  "version": "2.1.3",
  "summary": "Model framework for Cocoa and Cocoa Touch.",
  "homepage": "https://github.com/diaojunxian/Mantle.git",
  "license": "MIT",
  "authors": "txx",
  "source": {
      "git": "git@github.com:diaojunxian/Mantle.git",
      "branch": "mantle"
  },
  "platforms": {
    "ios": "8.0"
  },
  "requires_arc": true,
  "frameworks": "Foundation",
  "vendored_frameworks": "Carthage/Build/iOS/Mantle.framework"
  "prepare_command": "python mantle_build.py"
}

再在项目中新建 mantle_build.py 文件

#coding=utf-8
import os
import subprocess
import sys

path = os.path.join(os.path.abspath('.'))

file = open('Cartfile', 'w')
file.write('github "Mantle/Mantle"')
file.close()

command = 'carthage update --platform iOS'
subprocess.check_output(command, shell=True)

sys.stdout.write('Pass!\n')

这个脚本做到的就是将 mantle 的源码打成 framework 然后倒入到项目工程中。

其实,这里强调了很多关于 prepare_command 的内容,是因为它给了我灵感,我们上一大步已经实现了打成 Framework,这里如果只考虑引入的话,只需要两步

  • 第一步:将打包成 framework 推入我们之前的项目 pod 源码中,并将我们的 pod 库中的 podspec.json 改成:

    "name": "Mantle",
      "version": "2.1.3",
      "summary": "Model framework for Cocoa and Cocoa Touch.",
      "homepage": "https://github.com/diaojunxian/Mantle.git",
      "license": "MIT",
      "authors": "txx",
      "source": {
          "git": "git@github.com:diaojunxian/Mantle.git",
          "branch": "mantle"
      },
      "platforms": {
        "ios": "8.0"
      },
      "requires_arc": true,
      "frameworks": "Foundation",
      "vendored_frameworks": "Carthage/Build/iOS/Mantle.framework"
    

    然后执行验证

    pod lib lint Mantle.podspec.json --allow-warnings --verbose
    pod spec lint Mantle.podspec.json --allow-warnings --verbose 
    

    pod spec lint 和 pod lib lint 最主要的区别是,前者会根据 spec 文件 tag 信息去验证远程仓库代码是否存在,后者不会。简单理解就是,pod spec lint 联网检查,pod lib lint 不联网检查。

  • 第二步:向私有 Spec.ios 推送私有库

    pod repo push xxx Mantle.podspec.json --allow-warnings
    

    同样的不需要更改工程中的 podfile 内容,然后执行 pod install,这个时候工程中下载的依赖便是 framework。

引用方式

源码库中以 framework 形式存储了库,那么引用方式是否需要更改呢?

其实最标准的引用方式是:

#import <Mantle/mantle.h>

由于我们的工程中存在这大量不合理的引用方式,比如:

#import "mantle.h"

当我们以 framwork 方式引入后,可能会有报错,有一个简单的方法去解决:

平滑更改 Build Settings 中对应项 Header Search Paths,不影响当前所有项目的引用依赖方式。

这里的 recursive 是从根目录开始递归查询引用文件

然后就可以规避引用方式报错的问题,但是这样的引用还是有问题,需要我们开发进行更改。

打包时间是否优化?

当前我更改了 3 个库,打包的时间由 1 分钟左右的缩短,打包的体积未发生变化。

不知道算不算优化。

后续还需要将所有的引用库二进制化后观察。

参考

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 4 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 30 Jan 10:39

😂 我司用 carthage 经常被 github 限制

songz 回复

被墙了吗

swift 适用吗?我这边打包巨慢了

daivd 回复

只是将 pods 库都打成 framework,按理说 swift 都是要求使用 use frameworks 的啊,看来我研究还是不深入

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 13 Dec 14:44
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up