从接触iOS开发至今已有接近两年时间,在以往做过的项目中经常会需要引用三方的静态库,比如做分享,应用数据统计或地图等功能。静态库会暴漏部分头文件,但是我们无法看到实现的具体细节。虽然就我个人而言不太可能去做这种静态库,但是了解它的原理和制作过程还是非常有必要的。
iOS中的库
库是一种共享代码的方式,一般分为静态库和动态库
静态库的形式有.a和.framework。
动态库的形式有.dylib和.framework
注:自xcode 7开始,苹果用.tbd替换掉了.dylib。.tbd是一个文本文件,通过编译会生成.dylib。因为文本文件比二进制文件小很多,所以改用tbd之后安装包的体积会变小。
在ios8之前,苹果是不允许使用三方动态库的,所有那个时候开发者自己创建的framework默认都是静态库。ios8之后,苹果添加了framework动态库支持,但是在使用动态库的时候,使用的项目当中必须把该动态库添加到Embedded Framwork当中,否则会提示image not found.
动态库和静态库的区别
静态库在编译的时候被直接拷贝一份,拷贝到程序之后,这段代码不会再改变。静态库的好处是编译完成之后就可以直接运行,不受外部因素影响。缺点是目标程序体积会很大。
动态库在编译的时候不会被拷贝,程序只会存储指向动态库的引用,等到程序运行时才会被加载进去。动态库的优点是不需要拷贝到目标程序,所以程序体积不会受到影响,而且同一个库可以被多个程序使用。缺点也很明显,由于动态库是放在程序之外,所以动态库一旦被误删或版本变动就会影响到程序的运行。
.a和.framework的区别
一般来说共享库文件都是使用的静态库。而静态库又分.a文件和.framework文件。.framework实际上是一种打包方式,它将头文件,二进制文件和资源文件打包到一起。而.a文件是一个纯二进制文件,它必须配合头文件使用。因为使用.a文件一定要同时导入头文件,相比.framework更麻烦一点点,所以更推荐使用.framework。
静态库版本
静态库主要分为真机版和模拟器版 (另外在每个版本上还分Debug和Release版,一般提供给别人使用的都是Release版)
真机版和模拟器版的库并没有任何改动,仅仅只是对不同设备编译的结果。
而真正需要开发者注意的是静态库所支持的设备架构
设备架构主要有三种
armv7: iphone4S及以前的设备
armv7s:iphone 5,5c,ipad4
arm64:iphone 5S及之后的设备
注:模拟器只分32位(i386)和64位(x86_64)
如果你想做一个通用版的静态库(既支持模拟器又支持真机),方法有两种
1,分别生成两个版本之后 通过命令 lipo -create A B -output C来合成
2,新建Aggregate Target,通过运行脚本来合成(build phases ->Run Script)
脚本模板如下
# Sets the target folders and the final framework product.
# 如果工程名称和Framework的Target名称不一样的话,要自定义FMKNAME
# 例如: FMK_NAME = "MyFramework"
FMK_NAME=${PROJECT_NAME}
# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework
# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework
# -configuration ${CONFIGURATION}
# Clean and Building both architectures.
xcodebuild -configuration"Release"-target"${FMK_NAME}"-sdk iphoneos clean build
xcodebuild -configuration"Release"-target"${FMK_NAME}"-sdk iphonesimulator clean build
# Cleaning the oldest.
if[ -d"${INSTALL_DIR}"]
then
rm -rf"${INSTALL_DIR}"
fi
mkdir -p"${INSTALL_DIR}"
cp -R"${DEVICE_DIR}/""${INSTALL_DIR}/"
# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
lipo -create"${DEVICE_DIR}/${FMK_NAME}""${SIMULATOR_DIR}/${FMK_NAME}"-output"${INSTALL_DIR}/${FMK_NAME}"
rm -r"${WRK_DIR}"
open"${INSTALL_DIR}"
查看.a静态库
lipo XXXX.a -thin i386 -output XXX.i386
ar -t XXX.i386
打造自己的静态库
关于静态库的前期知识储备大概也就上面这些了,接下来我们就来做自己的静态库。
关于静态库的打包,网上教程实在太多,而且操作流程过于简单,基本上和创建工程没有太大区别,所以我就不再多说了。唯一需要注意以下几点:
1,记得把要暴漏的头文件放到指定位置
2,如果源文件中包含类别(category),则需要在使用的时候在Other linker Flags里面添加-Objc的标志,它的作用是把静态库中的所有和对象相关的文件都加载进来。否则链接器无法把原有方法和类别整合起来。在64位系统中则需要使用-all_load来加载所有文件。
当我简单地做完自己的.a和.framework静态库之后,显然还没过瘾,我开始思考有没有更复杂一点的玩法。这个时候我突然有了一个想法:如果我想做第二个B.framework,而这个库里面又包含了之前做的A.framework,能够实现吗?
原来这种嵌套的framework有一个专门的称呼:umbrella framework
可惜按照以上方法完成之后,爆出了一个警告"module 'xxx' does not include header 'xxx.h'"
简单一点说就是要在umbrella header 中引入所有需要暴漏的头文件
可是经过了两个多小时的折腾,xcode还是无法找到A.framework中的头文件。
所以我决定先记下这个问题,以后有空再来折腾。然后我换了一种方式来做这种嵌套framework,就是通过.a文件来创建。
我的做法可以说是十分非主流,因为我在.a静态库中引入了A.framework,然后将A.framework添加到了Copy Files中,然后打包生成B.a,结果居然可以使用。
以下就是我做的嵌套静态库demo地址
这是一个将图片转化为素描效果的静态库,你可以通过设置阀值来调整素描的强度,效果图如下