概念认识
什么是库
- 库是共享程序代码的方式,库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。在开发过程中,一些核心技术或者常用框架,出于安全性和稳定性的考虑,不想被外界知道,所以会把核心代码打包成库,只暴露出头文件以供使用。
- 库分为静态库和动态库两种。
- 静态库:链接时完整地拷贝至可执行文件中,使可执行文件体积变大。如果多个APP都使用了同一个静态库,那么每个APP都会拷贝一份。
- 动态库:链接时不拷贝至可执行文件中,可执行文件只会存储指向动态库的引用。程序运行时由系统动态加载到内存中,系统只会加载一次, 多个APP共用一份。
- 静态库的存在形式有两种:.a静态库、.framework静态库
- 动态库的存在形式有两种:.dylib动态库、系统的.framework动态库
系统的.framework是系统SDK库,有Foundation.framework、UIKit.framework、MapKit.framework等。由于苹果不开源,每个框架只提供了接口(.h文件),所有实现(.m文件或.c/.cpp文件)编译在一个.framework二进制文件中。
苹果把所有系统的.framework二进制文件统一打包到一个共享缓存文件中(dyld shared cache),共享缓存文件存储在手机路径(需要越狱才能找到):/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX。X代表ARM处理器指令集架构。
动态链接器(dyld)存储在手机路径:/usr/lib/dyld,用于在APP运行时,链接APP和系统的.framework。
两种.framework区别
- 静态库的形式包含.a和.framework两种形式,动态库的形式包含.dylib和系统的.framework两种形式。
动态库的.framework是系统SDK才有的库,而我们自己创建出来的.framework都是静态库
静态库中.a与.framework的区别
- .a是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件。
.a文件不能直接使用,需要.h文件配合。
.framework文件可以直接使用,因为本身包含了.h文件和资源文件。
.framework = .a + .h + sorrceFile(资源文件)
静态库的优点
- 实现程序的模块化,将固定的业务模块化成静态库。
- 方便共享代码,即可以和别人分享你的代码库,但别人又看不到你代码的实现。
- 开发第三方sdk的需要,例如两个公司之间业务交流,不可能把源代码都发送给另一个公司,这时候将私密内容打包成静态库,别人只能调用接口,而不能知道其中实现的细节。
编译链接
- 源代码转成目标文件的过程:
源代码先经过预处理器进行宏替换、删除注释等工作,得到.i文件,然后编译器对.i文件进行编译,编译成汇编语言程序(扩展名.s或.asm),之后再将汇编语言程序编译成机器指令(0和1组成),得到目标文件(扩展名.o)。 - 编译器和链接器处理流程:
源代码(.c或.cpp)→编译器→目标文件(.object)→链接器(+静态库)→可执行文件(.exe或Mach-O) - 链接器作用:
对于静态库,在编译链接时把多个目标文件和所用到的库文件链接在一起形成一个完整的可执行文件,可执行文件会比较大,但程序运行时就不需要库了。
对于动态库,在编译链接时把多个目标文件链接成可执行文件,但不会把库文件加入到该可执行文件中,而是在程序运行时链接库文件。
静态库制作
制作.a静态库
- .a文件是一种静态库,a是archive(归档)的缩写,它是由多个.o文件归档而成
- 新建工程,iOS --> Framework & Library --> Cocoa Touch Static Library
这里注意由于创建的是Static Library,Build Settings搜索linking,Mach-O Type默认为Static Library。
- 删除不需要的类,如StaticLibraryTool类,创建我们所需的类,如下:
- 添加需要公开的头文件
- 设置静态库需要支持的最低系统版本
- 修改配置 Active Architecture Only修改为NO,否则生成的静态库就只支持当前选择设备的架构。
- 接下来可以开始编译生成.a文件了。
设备选择Generic iOS Device,即通用型真机,然后command+B编译,在工程Products目录下生成.a文件,右键Show in Finder找到.a文件所在路径,图示Products目录下的libStaticLibraryTool.a从红色(红色表示文件不存在)变成了黑色(黑色表示文件存在),此时表示提供真机使用的静态库编译成功。
接着设备切换选择任一模拟器,command+B编译,此时提供模拟器使用的静态库编译成功,如下:
这两个.a文件要么只支持真机,要么只支持模拟器,如果想真机和模拟器都能使用该静态库,需要将二者合成为一个静态库,可用终端命令实现:
lipo -create 第一个.a文件路径 第二个.a文件路径 -output 最终的.a文件路径
合成为一个静态库后,我们可查看合成后的静态库支持的架构,用命令:lipo -info xxx.a
,看真机和模拟器架构是否都支持。结果如下:
"i386 x86_64"是模拟器架构,"armv7 arm64"是真机架构,说明该静态库两者都支持。
静态库支持哪些架构?
模拟器架构
iPhone4s ~ 5/5c : i386
iPhone5s以后机型 : x86_64
真机架构
3GS~4s : armv7
5/5c : armv7s(armv7兼容armv7s)
5s ~ iPhoneX : arm64
iPhone XS以后机型 : arm64e
- 如何使用.a静态库?
把.a文件和公开的头文件放入一个文件夹,命名为libStaticLibraryTool。将libStaticLibraryTool文件夹添加到需要使用的工程中,如下:
libStaticLibraryTool.a添加到工程后,
Build Pases->Link Binary With Libraries 和 General->Frameworks, Libraries, and Embedded Content都会自动引入libStaticLibraryTool.a。
Build Settings->Library Search Paths也会自动设置.a文件路径,如:$(PROJECT_DIR)/DemoA/libStaticLibraryTool。
由于静态库中使用了OC的分类(Category),OC链接器只会为类建立符号表,不会为分类建立符号表,导致函数调用时(运行时)找不到分类的符号表,无法获取函数地址,函数调用失败。
解决办法:设置Other Linker Flags的值为-ObjC,为分类建立符号表。
注意:如果不做该设置,项目中引用该库,访问该库的Category接口时会崩溃
最后我们便能在项目中引入头文件,使用.a静态库的功能了,如下:
- Xcode13制作静态库,左侧的Products目录不见了,其实Products是存在的,只是没有展示出来。
解决方式:对.xcodeproj右键“显示包内容”,打开project.pbxproj,command+f搜索mainGroup,复制mainGroup的值,将其粘贴到productRefGroup的值上,使两者保持一致,这样工程左侧的Products目录就显示出来了。
制作.framework静态库
- 新建工程时选择Framework
该Framework默认是Dynamic Library(动态库),我们需要将其设置成静态库:Build Settings搜索mach,Mach-O Type选择Static Library。
往工程中添加Person类和UIImage+Extension类,StaticLibraryTool.h中引入Person类和UIImage+Extension类的头文件,推荐用包含静态库名的尖括号,指明引入的是哪个静态库的头文件:
Person.h和UIImage+Extension.h需要暴露出来,这两个头文件默认在TARGETS->Build Phases->Headers的Project中,将它们拖拽到Headers的Public中,如下:
设置Build Settings中的Active Architecture Only为NO,iOS Deployment Target的最低版本为iOS 9.0,然后command+B,编译生成.framework静态库。
- 如何使用.framework静态库?
使用时,将StaticLibraryTool.framework拖入到需要使用的工程中。
Build Settings->Other Linker Flags设置-ObjC,以便编译器能加载OC的Category(若不配置这个,访问Category的接口时程序会崩溃)。
由于静态库StaticLibraryTool.h中引入的头文件带上了静态库名作为路径,即如下方式
#import <StaticLibraryTool/Person.h>
#import <StaticLibraryTool/UIImage+Extension.h>
因此Build Settings->Header Search Paths可不用设置静态库StaticLibraryTool的路径。使用静态库时,必须严格按照上述带静态库名的方式引入头文件。
如果想不带静态库名引入,如#import "StaticLibraryTool.h"(不推荐,无法明确这是静态库的头文件),则需要配置Header Search Paths,添加路径:$(PROJECT_DIR)/DemoA/StaticLibraryTool.framework/Headers
实现静态库的注意事项
- 无论是.a静态库还是.framework静态库,我们需要的都是二进制文件+.h文件+资源文件,不同的是,.a本身只是二进制文件,需要配上.h文件和资源文件才能使用,而.framework本身已经包含了二进制文件、.h和资源文件,可以直接使用。
- 图片资源的处理:两种静态库,一般都是把图片文件单独的放在一个.bundle文件中,一般.bundle的名字和.a或.framework的名字相同。新建一个文件夹,把它改名为.bundle,右键->显示包内容,之后就可以向其中添加资源文件。
- 把category打成静态库,在使用静态库的工程中,调用category中的方法时会报找不到该方法的错误(selector not recognized),解决办法是:在使用静态库的工程中配置Build Settings->Other Linker Flags的值为-ObjC。
- 如果一个静态库很复杂,需要暴露的.h文件比较多,则可以在静态库的内部创建一个.h文件(这个.h文件的名字一般和静态库的名字相同)统一管理,把所有需要暴露出来的.h文件都集中放在这个.h文件中,这样使用时可以只引入该.h文件。
👉🏻项目地址:iOS-StaticLibrary
如果文章和项目对你有帮助,还请给个Star⭐️,你的Star⭐️是我持续输出的动力,谢谢啦😘