什么是库
库是共享代码的一种方式。从本质上来说库是一种可执行代码的二进制格式,可以被载入内存中执行。在开发过程中,一些核心技术或者常用框架,出于安全性和稳定性的考虑,不想被外界知道,所以会把核心代码打包成库,只暴露出头文件以供他人使用。
库的分类
库分静态库和动态库两种。
- 静态库
静态库有 .a
和 .framework
两种形式。
.a
是一个纯二进制文件,要有 .h
文件以及资源文件配合, .framework
中除了有二进制文件之外还包含了资源文件,因此可以直接使用。总的来说,.framework
整合了 .a + .h + resources
,所以创建静态库最好还是用 .framework
的形式。
- 动态库
存在 .framework
和 .tbd
两种形式。
在 iOS8 之前,苹果不允许第三方框架使用动态方式加载,从 iOS8 开始允许开发者有条件地创建和使用动态框架,这种框架叫做 Cocoa Touch Framework。虽然同样是动态框架,但是和系统 framework 不同,app 中的使用的 Cocoa Touch Framework 在打包和提交 app 时会被放到 app bundle 中,运行在沙盒里,而不是系统中。也就是说,不同的 app 就算使用了同样的 framework,但还是会有多份的框架被分别签名,打包和加载。
库的优劣
静态库:在链接时会被完整地复制到可执行文件中,被多次使用就有多份冗余拷贝。
好处很明显,编译完成之后,库文件实际上就没有作用了。目标程序没有外部依赖,就可以直接运行。当然其缺点也很明显,就是会使用目标程序的体积增大。
动态库:与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,库才会被动态加载进来。系统的动态库不需要拷贝到目标程序中,自建的动态库可以由工程内的多个库共享,因此可以减小程序的体积。但是,由于其把静态链接做的事情都搬到运行时来做,程序的启动会变慢。
库的创建
下面演示动态库的创建过程。
你可以单独的为你的共享文件创建一个项目,也可以在已有的项目基础上添加静态库项目。我们这里采用后者。
- 创建 MyFramework
创建好之后会为你添加一个 Framework 项目文件夹,并为你创建好了 MyFramwWork.h
文件,该文件用来存放你想要分享的文件。
- 添加共享文件
选中你的 Framework 项目,添加你想要共享的文件,需要同时添加 .h
和 .m
文件。
- 导入共享的文件信息,可选
#import <Foundation/Foundation.h>
//! Project version number for MyFramework.
FOUNDATION_EXPORT double MyFrameworkVersionNumber;
//! Project version string for MyFramework.
FOUNDATION_EXPORT const unsigned char MyFrameworkVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <MyFramework/PublicHeader.h>
#import <MyFramework/Tool.h> // 导入工具
- 配置 Framework 信息
配置支持的机型,默认即可。
配置库的类型为动态库。
-
配置 scheme,可选
这一步决定了 Framework 是 Debug 还是 Release。
运行
选中 MyFramework 项目,运行环境决定了你所支持的环境。
- 获取 Framework
找到 Products
文件夹中的 MyFramewoek.framework
右键 Show in Finder
查看。
可以看到,这里分别编译出了
Debug
下面的真机和模拟器包。
- 使用
选择某一种环境的 Framework,拉入测试项目,进行如下设置。
导入库的头文件信息,测试库中类的使用情况。
#import <MyFramework/MyFramework.h>
[[Tool new] print];
注:如果你的库依赖了系统库,则使用者也应当设置对应的依赖库。
静态库
静态库和动态库的创建的过程基本一直,只需要将 Mach-O Type
设置为 Static Library
。
库中的资源使用
关于库中资源使用的问题依旧是值得一提的。项目中的获取资源时,如果没有特地指定 bundle
默认都是在主项目 mainBundle
,但是,如果你将文件放在 framework
中,那么资源读取就需要特别指定了。
下面通过图片资源获取来演示。
首先为你的资源创建自己的 bundle
文件夹并放入一张图片资源。
那么图片资源的使用如下。
[UIImage imageNamed:[[[NSBundle mainBundle] pathForResource:@"Frameworks" ofType:@"bundle"] stringByAppendingString:@"/Images/author.png"]]
[UIImage imageNamed:@"Frameworks.bundle/Images/author.png"]
对于其他资源的获取,如 Xib,可能会涉及到 bundle 问题,这里推荐使用方法 -bundleForClass:
获取。
NSBundle* bundle = [NSBundle bundleForClass:Tool.class];
真机库和模拟器库的合并
通过模拟器编译的库无法在真机下使用,反过来也是一样。当你企图在真机情况下使用模拟器的库时,你会得到类似如下错误。
ld: symbol(s) not found for architecture arm64
这的 arm64
时真机的类型,在设置库所支持的类型时可以进行设置。
模拟器的类型有:i386 和 x86_64。
那么,将两种库进行合并,以适应任何情况下的运行是非常有必要的。
查看库所支持的类型
通过终端进入到 framework 文件夹下,输入以下命令
lipo -info 库名称
$ lipo -info MyFramework
Architectures in the fat file: MyFramework are: i386 x86_64
$ lipo -info MyFramework
Architectures in the fat file: MyFramework are: armv7 arm64
这里合并介绍两种方式,一种是手动使用命令合并,另外一种用脚本自动完成合并。
- 手动合并
在编译完两种 framework 后,我们通过下面的命令完成合并。
lipo -create 真机xxx.framework/xxx 模拟器xxx.framework/xxx -output 真机xxx.framework/xxx
上述命令表示将真机和模拟器下的xxx(指代你的项目)合并并输出替换真机framework下的xxx(也可以是模拟器下的)。
完成之后验证新的 framework。
$ lipo -info MyFramework
Architectures in the fat file: MyFramework are: i386 x86_64 armv7 arm64
可以看到,新的 MyFramework 同时支持了真机和模拟器。
- 脚本合并
手动合并的麻烦之处在于,手动完成两种类型的库的编译以及合并。那么就使用脚本解放你的双手吧。
创建 Aggregate
添加脚本
以下为脚本的信息,你仅需改变以下库名称即可,其中的注释已经非常明确。
# 配置两种目标文件路径和最终 framework 路径
FMK_NAME="MyFramework"
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework
# 临时的build文件夹
WRK_DIR=build
# 真机framework路径,注:这里是 release
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
# 模拟器framework路径注:这里是 release
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework
# xcode命令,编译项目
xcodebuild -target "${FMK_NAME}" clean
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos
# 如果已存在,那么删除创建新的
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
# 复制真机framework文件到最终的路径,以便后面合并使用
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
# 使用 Lipo 工具合并
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
# 删除临时文件夹,如果你想保留两种 framework,请注释下述命令
rm -r "${WRK_DIR}"
# 打开最终的 framework
open "${INSTALL_DIR}"
然后编译这个 Aggregate 项目即可。编译完成会自动打开库所在的文件夹。
总结
framework 分动态库和静态库两种,动态库动态连接,只有引用,因此运行速度慢于静态库,静态库编译时会拷贝一份,提前编译,速度快,但是因此拷贝的原因,包大小会增加。framework 同样分了模拟器库和真机库,我们通常需要将两者进行合并,方式可以通过手动也可以使用脚本自动,无论是何种方式,核心命令是一致的。