参考了以下几篇文章(谢谢各位作者大大的内容):
http://www.jianshu.com/p/155a3cfb765e?utm_campaign=hugo 包含第三方静态库的framework
http://www.cocoachina.com/ios/20150226/11182.html
一. 静态库和动态库的详细介绍
下面分别介绍静态库、动态库,Framework和.a以及.dylib/.tbd区别
一). 静态库与动态库
首先要解释一下什么是库,库(Library)其实就是一段编译好的二进制代码,加上头文件就可以供别人使用,一般会有两种情况要用到库:
某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件。
对于某些不会进行大的改动的代码,比方说很多大公司常用且很少变动的模块都会编译成库,这样做的好处一是可以节省编译时间,二来对于代码的管理也非常方便。
因为库是已经编译好的二进制文件了,编译的时候只需要link一下,既然提到了link那就有不同的形式了,静态和动态,与之相对应的就是静态库和动态库。
1. 静态库
平时我们用的第三方SDK基本上都是静态库,静态库的几个特点:
在App项目编译的时候会被拷贝一份编译到目标程序中,相当于将静态库嵌入了,所以得到的App二进制文件会变大。
在使用的时候,需要手动导入静态库所依赖的其他类库。(比如说某个SDK中使用到了CoreMotion.framework,在使用的时候需要手动导入。有的SDK需要link十几个系统库,这个时候比较麻烦,只能一个一个手动加,这是静态库一个很大的不便之处。)
导入静态库的应用可以减少对外界的依赖,如果导入的是第三方动态库,动态库找不到的话应用就会崩掉,例如Linux上经常出现的lib not found。
静态库很大的一个优点是减少耦合性,因为静态库中是不可以包含其他静态库的,使用的时候要另外导入它的依赖库,最大限度的保证了每一个静态库都是独立的,不会重复引用。
2. 动态库
这个是我们最常用的一类库,使用频率最高的UIKit.framework和Fundation.framework都属于动态库,所有.dylib和.tbd结尾的都属于动态库。动态库的几个特点:
平时使用的系统库都放在iOS系统中,在你打包应用程序的时候这些库不会拷贝到你的程序中,当需要使用的时候会动态从iOS系统中加载它们,因为这个原因,动态库也被称作共享库。编译时才载入的特性,也可以让我们随时对库进行替换,而不需要重新编译代码。
这些库是所有应用公用的,换一种说法就是节省了应用安装包的体积,这是区别静态库很重要的一个特点,因为静态库使用一次就要拷贝一次,非常浪费资源。
动态库在制作的时候可以直接包含静态库,也能自动link所需要的依赖库。
使用动态库的时候不需要再次link依赖库,即导即用,这个就厉害了。唯一需要注意的是在导入自己制作的动态库时,需要在Embedded Binaries中导入,不然会报错:image not found。此时这个动态库会跟静态库一样被拷贝到目标程序中进行编译,苹果又把这种Framework叫做Embedded Framework
关于动态库要搞清楚一点,我们自己制作的动态库与系统动态库的区别,我们自己制作的动态库引入App项目的时候需要embed进项目,也就是要拷贝到目标程序中,这就有点不像动态库的特性了,苹果这么做也是考虑安全问题吧!
总结:同一个静态库在不同程序中使用时,每一个程序中都得导入一次,打包时也被打包进去,形成一个程序。而动态库在不同程序中,打包时并没有被打包进去,只在程序运行使用时,才链接载入(如系统的框架如UIKit、Foundation等),所以程序体积会小很多,但是苹果不让使用自己的动态库,否则审核就无法通过。
二). Framework、.a、.dylib/.tbd
1. Framework
Framework的英文释意是框架,主要由Headers、binary文件、.bundle这三部分构成,除此之外还有Info.plist和Modules,后两者主要记录Framework的版本之类的信息,一般都会删掉,不做讨论
Headers包含我们在制作Framework的时候暴露的头文件,所有被暴露的.h都放在这里。
binary文件整个Framework的核心,所有代码都被编译成了这样一坨二进制文件,这里要注意的是添加的依赖库不会被编译进来,用的时候还需要重新link其他依赖库。
.bundle资源文件都打包放在这里。在制作Framework的时候不可以把图片直接放在项目中,否则制作好之后图片是一张一张的出现在项目中非常乱,需要新建一个bundle将图片放进去,这里的bundle提供整个SDK的图片资源。注意:图片放进bundle之后不可以用[UIImage ImageWithName:]读取图片。要先找到bundle包再拿图片。
这里要纠正一个误区
很多人认为系统的Framework就是动态库,我们自己制作的Framework就是静态库。
其实Framework既可以是静态库也可以是动态库,这取决于编译成的Mach-O(就是那个二进制文件)是动态库还是静态库,Framework本质上并不是一个库,它是苹果为了方便开发者提供了一种库的打包方式,Framework会将Mach-O文件、头文件和资源包全都包含进来,不需要再手动整理,我们也可以通过Xcode来制作framework动态库使用。所以总结: Framework是库的打包形式,既可以是动态库也是静态库。
2. .a静态库
这类静态库与Framework基本类似,不同的是在打包成.a文件的同时,还需要提供头文件,使用时相较于Framework比较麻烦,(例如微信支付SDK使用的是.a,不同的是支付宝SDK是以framework的形式打包的)。.a这样打包不够方便,而Framework编译完成暴露的头文件都已经放好了。
3. .dylib/.tbd 动态库
这类动态库我们也经常用,基本上都是系统提供的,一般不能自己制作,就算通过其他方式制作使用,也肯定不能上架。所以这里不作细述!
二. Framework的制作
动态库与静态库的制作流程基本一样,包括头文件的暴露等,唯一不同的是Mach-O文件的编译形式。本节将介绍Xcode制作Framework的过程,本次制作的Framework静态库依赖其他第三方静态库(Framework和.a)。
1> 新建工程新建Framework工程这里要选Framework &Library —> Cocoa Touch Framework,如果选择的Static Library制作出来的是.a静态库(.a静态库将在下一节做说明)。
2> 导入所有要打包的文件和其他第三方静态库正常导入要打包的文件就可以了,在导入第三方静态库的时候要注意,不要选择添加到target中,如果添加进去要去target里面把第三方静态库删掉(只需导入,不要添加进target)导入第三方静态库导入第三方静态库之后再link依赖的系统库,像这样link依赖库注意运行目标,因为我用的是Xcode8,最低支持到iOS8。要打包的文件和第三方静态库全部导入完成所有文件导入情况
3> 暴露头文件将头文件暴露出去,供外界使用,所有的编译文件都在Build Phases ->headers -> Project中,需要添加到public里面暴露头文件
4> 选择Mach-O的编译方式这是最重要的一步,这一步决定我们制作出来的是静态库还是动态库,默认选择的是Dynamic Library,要手动选择Static LibraryMach-O 形式
5> 编译如果你的依赖库里面有lib开头的dylib动态库,此时应该会报错动态库链接报错因为tbd是苹果提供的新的动态库格式,之前都是dylib
6> tbd动态库报错修改先把原来的.tbd删掉,然后再次添加,这个时候选择add other,在弹出的窗口中按快捷键shift + command + G 调出finder的前往窗口,输入/usr/lib,然后添加相应的dylib动态库修改的动态库替换完成之后重新编译项目,生成Framework(可在Product文件中右击在finder中显示找到)
7> 使用新建一个文件夹,将制作好的静态库拷贝出来放进去,再将第三方静态库拷贝到相同的文件夹中,此时只要将这个文件夹提供给外界使用就可以了,这是我写的测试demo验证打包好的SDK是否可以正常使用制作完成使用至此我们已经完成了Framework中包含其他第三方静态库的制作。如果需要制作动态库,只需要在第5步中将Mach-O的形式改为Dynamic Library就可以了,其他步骤一样
合并模拟器版framework和真机版frameworkframework静态库合并的不是framework,而是framework下的一个二进制文件。
lipo -info framework下二进制文件的绝对路径查看一下库的信息
模拟器: i386 x86_64真机: armv7 arm64
lipo -create 第一个framework下二进制文件的绝对路径 第二个framework下二进制文件的绝对路径 -output 最终的二进制文件路径。本文中使用的命令如下:
lipo -create /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-clvayfrjgytqrbdkyqrtcjkxfeuz/Build/Products/Release-iphonesimulator/FMDB.framework/FMDB /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-clvayfrjgytqrbdkyqrtcjkxfeuz/Build/Products/Release-iphoneos/Release-iphoneos.framework/FMDB -output /Users/harvey/Desktop/FMDB
最后将任何一个framework中的二进制文件替换成合并后的二进制文件即可。把framework添加到要使用的项目中即可使用。
注意:如果创建的framework中使用了category类,则在使用framework的项目配置中【Other Linker Flags】需要添加参数【-ObjC]或者【-all_load】。如果使用framework的使用出现【Umbrella header for module 'XXXX' does not include header 'XXXXX.h'】,是因为错把xxxxx.h拖到了public中。如果出现【dyld: Library not loaded:XXXXXX】,是因为打包的framework版本太高。比如打包framework时,选择的是iOS 9.0,而实际的工程环境是iOS 8开始的。
三、创建.a静态库
第一步,新建工程。一般使用工程名就使用库的名称,比如我这里用Pay来创建静态库,我的工程名就取名为Pay,创建的.a静态库就是libPay.a。
新建.a静态库工程这里要选Framework &Library —> Cocoa Touch Static Library,如果选择的framework制作出来的是framework工程(上一节已经说明的很详细了,这里不过多描述)。
第二步,删除系统默认创建的【Pay.h】和【Pay.m】文件,导入需要打包的源文件。
第三步,修改导出product配置
修改编辑配置为release模式
第四步,修改编译指令集
设置Release为NO
模拟器: i386 x86_64真机:armv7 arm64如果第五步这里,设置为YES,那么编译出来的.a静态库就只包含当前设备的指令集。举个例子:如果我们选择iPhone 5模拟器【Command+B】编译,则编译出来的.a静态库只能用iPhone4s~5模拟器跑程序,用iPhone5s~6plus,则会报找不到x86_64的libFMDB库。设置为NO,则会把所有指令集的都打包合并。
第五步,编译(快捷键【Command+B】编译时,需要用模拟器和真机各编译一次,这样Products目录下的libPay.a静态库才会变为黑色,右键show in Finder,可以进入Products目录下。
为什么需要用模拟器和真机各编译一次呢?可以看到Products目录下有【Release-iphoneos】和【Release-iphonesimulator】两个文件件。前者里面是真机使用的.a静态库,后者是模拟器使用的.a静态库。
注意:如果步骤三中,不将Build Configuration改为Release,则打包出来的静态库会存于【Debug-iphoneos】和【Debug-iphonesimulator】两个文件夹下。我们一般都使用Release模式,因为程序最终发布之后是Release版的,所以静态库也是在Release模式下使用。
如果想要通用需要将模拟器使用的静态库与真机使用的静态库合并成一个静态库,可以使用终端命令来实现。命令格式:lipo -create 第一个.a文件的绝对路径 第二个.a文件的绝对路径 -output 最终的.a文件路径。本文中使用的命令如下:
lipo -create /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-ctegiztcjikewoeprxxtmryzetfa/Build/Products/Release-iphoneos/libFMDB.a /Users/harvey/Library/Developer/Xcode/DerivedData/FMDB-ctegiztcjikewoeprxxtmryzetfa/Build/Products/Release-iphonesimulator/libFMDB.a -output /Users/harvey/Desktop/libFMDB.a
补充:经过多次实践,第三步的操作省略,依然可以导出可正常使用的包。如果静态库中有category类,则在使用静态库的项目配置中【Other Linker Flags】需要添加参数【-ObjC]或者【-all_load】。