二、cocopods 中的动态库、静态库和framework

写在前面

前文中,梳理了iOS 平台下的 动态库静态库framework之间的关系,可以用如下一句话概括:

  • 就是一个二进制文件,有动态和静态之分
  • framework 是的一种打包方式

在实际开发场景中,我们使用 cocopods 这个工具进行代码组件化管理:

  • 我们使用 cocopods 创建 pod 库进行代码组件开发,然后通过 podfile 在其它地方使用
  • 使用 cocoapods-packager 工具将 pod 库打包成 framework 对外输出

本文从如下两个大的方面来介绍,cocopods 中的动态库、静态库:

image.png

本文使用的 cocopods 版本是 1.5.2,Xcode 版本是 Version 10.1 (10B61)。

➜  iOSFrameworkDemo git:(master) ✗ pod --version
1.5.2

1. 制作 pod 库 WBSDK

制作教程网上有很多,这里就不 step by step 来做了。

CocoaPods 私有仓库的创建(超详细)

这里,创建一个名为 WBSDK 的 pod 库来演示。

2. 使用 WBSDK

使用 pod lib create 工具提供的模板创建的仓库中,Example 工程的 podfile 文件如下:

use_frameworks!

platform :ios, '8.0'

target 'WBSDK_Example' do
  pod 'WBSDK', :path => '../'

  target 'WBSDK_Tests' do
    inherit! :search_paths
  end
end

我们把最外层的 Example 工程称之为壳工程,壳工程要使用 pod 依赖 WBSDK。

➜  WBSDK git:(master) ✗ tree -L 2
.
├── Example
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   ├── Tests
│   ├── WBSDK
│   ├── WBSDK.xcodeproj
│   └── WBSDK.xcworkspace
├── LICENSE
├── README.md
├── WBSDK
│   ├── Assets
│   └── Classes
├── WBSDK.podspec
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj

13 directories, 6 files

壳工程使用 WBSDK 的时候,并不是直接以源码的方式引入使用,而是以二进制库的方使用的

壳工程使用 WBSDK 的方式有两种:

  • 静态库
  • 动态库

这个通过 podfile 中的 use_frameworks! 控制。

2.1 以动态库的方式使用 WBSDK

首先,在 podfile 中写入 use_frameworks!,执行 pod install 命令,运行 Example 工程。

在 Xcode 中查看运行日志,某一次运行的截图如下:

image.png

上图中,我们可以看出 Build 的基本顺序入下:

  • Project Pods 下面的 target
  • Project WBSDK 下面的 target

现在,我们需要观察一下 Pods 这个 project,我们聚焦于 WBSKD.framework 上面。

image.png

我们发现,WBSKD.framework 的 mach-o type 是以 dynamic 的方式打包。
我们可以查看 Example 运行后的 .app 文件结构,查看结构如下:

➜  WBSDK_Example.app tree -L 2
.
├── Base.lproj
│   ├── LaunchScreen.storyboardc
│   └── Main.storyboardc
├── Frameworks
│   └── WBSDK.framework
├── Info.plist
├── PkgInfo
├── WBSDK_Example
├── _CodeSignature
│   └── CodeResources
└── en.lproj
    └── InfoPlist.strings

7 directories, 5 files

WBSDK.framework 独立于 WBSDK_Example 这个可执行文件存在,也验证了我们上面的说法。

2.2 以静态库的方式使用 WBSKD

我们去掉 podfile 中的 use_framework! 之后,再次执行 pod install,然后重新运行代码。

使用同样的方式来验证 WBSDK 的引入方式,我们发现,此时壳工程以静态库 .a 的形式来依赖 WBSDK。

验证方式与 2.1 相同,此处不赘述。

3. 使用 cocoapods-packager 工具打包 framework

3.1 安装 cocoapods-packager

cocoapods-packagerGitHub 地址。

官方给出了安装教程,安装完成之后,在终端输入 pod package,查看这个工具的基本使用如下。

 $ pod package NAME [SOURCE]

两个基本参数:

  • NAME: 需要打包仓库名称,必填参数
  • SOURCE: pod repo地址,可选参数,默认是官方 repo

3.2 相关的参数

这里,我们只看和打包输出framework类型相关的参数 ,我们这里结合源码来理解这些参数。

这里我们主要研究三个参数,以及默认不传参数的情形:

  • --embedded
  • --library
  • -dynamic
  • 默认不传

package.rb 中,我将和打包类型相关的源码抠出来,分析一下。

  ['--embedded',  'Generate embedded frameworks.'],
  ['--library',   'Generate static libraries.'],
  ['--dynamic',   'Generate dynamic framework.'],

--library--dynamic

从三个参数的名称和解释中,这两个是没有歧义的:

  • --library,表示以 .a 的形式输出静态库
  • --dynamic,表示以 .framework 的形式输出动态库,按照之前我们的分类标准,是 Embedded Framework,如下图所示
image.png

那么问题来了,--embedded 是个什么东东?用这个参数打出来的究竟是动态库还是静态库?

--embedded

我们查看 initialize(argv) 函数。

def initialize(argv)
@embedded = argv.flag?('embedded')
@library = argv.flag?('library')
@dynamic = argv.flag?('dynamic')

@package_type = if @embedded
                  :static_framework
                elsif @dynamic
                  :dynamic_framework
                elsif @library
                  :static_library
                else
                  :static_framework
                end
# ……
end

builder.rb 中,build() 函数声明如下:

def build(package_type)
  case package_type
  when :static_library
    build_static_library
  when :static_framework
    build_static_framework
  when :dynamic_framework
    build_dynamic_framework
  end
end

综上,我们发现使用 --embedded 参数,会以 .framework 的形式输出静态库,按照之前我们的分类标准,也就是 Static Framework,如下图所示:

image.png

从源码中也可以看出,在不带参数的默认情况下,输出的也是 Static Framework。

默认参数和 --embedded 的区别

我们通过上面源码发现,不带参数 和加上--embedded 参数结果都是打包静态framework,它们有区别么???

换言之,我们能说默认参数是 --embedded 么?

答案是否定的,在目录打包输出的目录结构上还是有些许差异。

下面抠出了相关代码,这部分代码结合3.4一起看。

def framework_path
  if @embedded
    @spec.name + '.embeddedframework' + '/' + @spec.name + '.framework'
  else
    @spec.name + '.framework'
  end
end
# package.rb
builder.build(@package_type)

return unless @embedded
builder.link_embedded_resources
# builder.rb
def link_embedded_resources
  target_path = @fwk.root_path + Pathname.new('Resources')
  target_path.mkdir unless target_path.exist?

  Dir.glob(@fwk.resources_path.to_s + '/*').each do |resource|
    resource = Pathname.new(resource).relative_path_from(target_path)
    `ln -sf #{resource} #{target_path}`
  end
end

总结

cocoapods-packager 通过三个参数来控制打包类型的:

  • --library,以 .a 的形式输出静态库
  • --dynamic,以 .framework 的形式输出动态库,也就是我们前文讲 的 Embedded Framework
  • --embedded.framework 的形式输出静态库,对应我们前文讲 的 Static Framework
  • 默认不传参数,也会输出 Static Framework。

这里需要注意:--embeddedEmbedded Framework 的区别,不要混为一谈,二者有本质区别。

3.4 使用 cocoapods-packager 打包

为了测试方便,我们直接使用本地代码来打包,修改一下 WBSDK.podspec 中的Source 地址,指向本地仓库。

s.source = { :git => '/Users/xieshoutan/Code/Blog/pods/WBSDK' }

我们在 pod package 命令中加入 --force 参数,本次打包脚本强制执行,会覆盖上一次打包结果。

打包 Static Framework(默认不带参数)

使用默认 pod package 命令打包静态库:

➜  WBSDK git:(master) ✗ pod package WBSDK.podspec --force

打包输出的文件放在 WBSDK-0.1.0 文件夹下,查看输出结果:

├── WBSDK-0.1.0
│   ├── WBSDK.podspec
│   ├── build
│   │   ├── Pods.build
│   │   └── XCBuildData
│   └── ios
│       └── WBSDK.framework

我们可以验证 WBSDK.framework 是一个静态库。

build 文件夹下面是本次编译打包的日志。

打包 Static Framework(--embedded)

使用 pod package 命令加上--embedded,打包静态库:

➜  WBSDK git:(master) ✗ pod package WBSDK.podspec --embedded --force

打包输出的文件放在 WBSDK-0.1.0 文件夹下:

├── WBSDK-0.1.0
│   ├── WBSDK.podspec
│   ├── build
│   │   ├── Pods.build
│   │   │   └── Release-iphonesimulator
│   │   └── XCBuildData
│   │       ├── BuildDescriptionCacheIndex-5324f43b9a88d4ed571fe98b8ba8ab33
│   │       ├── build.db
│   │       ├── f2a06309409592aa39514fee692f5f80-desc.xcbuild
│   │       └── f2a06309409592aa39514fee692f5f80-manifest.xcbuild
│   └── ios
│       └── WBSDK.embeddedframework
│           ├── Resources
│           └── WBSDK.framework

我们发现,和不带参数的静态库相比,这次打包结果文件目录结构发生了一些变化:

  • WBSDK.embeddedframework 对输出结果进行了一次封装
  • WBSDK.framework 同级目录多了一个 Resource 文件夹,如果在 .podsec 中有资源文件引入,Resource 文件夹下会有对应的 bundle 文件

上一节最后列出了与之相关的源码,可以结合查看,辅助理解。

打包 Embedded Framework(--dynamic)

使用 pod package 命令打包动态库,后面添加一个 --dynamic 参数。

➜  WBSDK git:(master) ✗ pod package WBSDK.podspec --dynamic --force

打包输出的文件的动态库放在 WBSDK-0.1.0 文件夹下:

├── WBSDK-0.1.0
│   ├── WBSDK.podspec
│   └── ios
│       ├── WBSDK.framework
│       └── WBSDK.framework.dSYM

4. 总结

至此,我们在本篇中,研究了和cocopods相关的动态库、静态库以及framework的基础知识。

但是对于“资源引用”、“pod库中动态库、静态库或者framework的引用”等知识没有涵盖。这一部分内容会在后面的文章中讲述。

参考资料

https://developer.apple.com/library/archive/technotes/tn2435/_index.html

cocoapods的静态库和动态库

Embedding Frameworks In An App

Pod Authors Guide to CocoaPods Frameworks

Link Binary with libraries VS Embed Frameworks

An Introduction to Creating and Distributing Embedded Frameworks in iOS

How do I use Cocoapods in an embedded framework?

Name Mangling

CocoaPods 都做了什么?

推荐阅读更多精彩内容