在iOS开发中,将特定功能代码封装在一个库中,对外提供接口调用,这样方便维护和集成,如网络库。库有静态库和动态库,我们在集成时该选择哪种?制作自己的库时,该如何指定?
一、问题引出
在使用CocoaPods管理三方库时,Podfile
文件中关于use_frameworks!
的使用有以下特点:
说明:
s.static_framework = true\false
用于在库的podspec文件中声明是否要生成静态库。
Podfile选项 \ Podspec | s.static_framework = true | s.static_framework = false |
---|---|---|
use_frameworks! | 静态库 | 动态库 |
不使用use_frameworks! | 静态库,swift项目导入库时报错,OC不会 | 静态库,swift项目导入库报错,OC不会 |
use_frameworks! :linkage => :static | 静态库 | 静态库 |
use_frameworks! :linkage => :dynamic | 静态库 | 动态库 |
结论:
1.swift项目需要使用
use_frameworks!
选项,后面可以接:linkage => :static等。
2.库通过s.static_framework = true
指定了生成静态库,那么集成时就会是静态库。
3.库没有通过s.static_framework = true
指定静态库,那么集成时可以通过上述表格中方式控制。
现在我们知道了如何通过pod控制集成动、静态库,那么动态库、静态库究竟有啥区别?
二、动、静态库简单对比
对比项 | 静态库 | 动态库 |
---|---|---|
文件格式 |
.a 、framework 、xcframework
|
.tbd 、.dylib 、framework 、xcframework
|
编译链接 | 链接时合并到可执行文件中;推送扩展等插件依赖了该库也会拷贝一份 | 编译链接时不合并,会独立生成一个动态库类型的Mach-O文件,放在xxx.app/Frameworks/xxx.framework中;其它扩展、插件依赖了不会再生成一份 |
对启动影响 | 内容跟随主二进制加载到内存,对启动影响较小 | 启动时dyld会加载其Mach-O并进行符号解析,相比静态库更耗时 |
包体积 | 如果主工程依赖该库而扩展插件未依赖,那么包体积会相比做成动态库小一点 | 相比静态库要大一丁点 |
三、了解静态库
Static libraries are collections or archives of object files.
即静态库是.o文件的集合或归档。
1. iOS工程中的静态库
静态库主要文件形式是.a文件,以及静态库类型的framwork。使用CocoaPos集成的库,如果在Podfile中写了:
install! 'cocoapods',
# 禁用输入输出路径,不在生成的 Xcode 项目中包含特定的输入输出路径,从而避免一些可能的兼容性问题。
disable_input_output_paths: true,
generate_multiple_pod_projects: true # 让每个pod依赖库成为一个单独的项目引入,这样大大提升了编译速度
那么每个pod install
后每个库会对应一个工程,可以通过在对应工程的build setting
中查看Mach-O type
确定静态库还是动态库。
关于Mach-O基本了解可以查看:iOS中Mach-O概览
2. framework
静态库和.a
的区别
》.a是一个纯二进制文件,.framework中有二进制文件、头文件、资源文件、模块文件Modules。
》常规开发时,.a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用。
》.a + .h + sourceFile + Modules = .framework。
纯swift生成静态库时,如果生成.a
静态库无法直接使用,建议生成framework
静态库。(swift语言生成.a文件后,我们拖入项目中使用会发现import
模块会报错:No such module 'XXX'
)
3. 制作静态库
3.1手动制作一个静态库
你自己根据网上资料的方法操作生成,但如果给其他库中定义的类增加OC分类时,需要注意是否需要增加-Objc编译标识。
3.2 通过cocoapods
方法一:
在库的podspec
文件中声明生成静态库,这样在pod install
之后,xcode最终会编译为静态库。
# 在库的podspec文件中写如下设置
s.static_framework = true
方法二:
在Podfile中集成库时声明:
use_frameworks! :linkage => :static
或者另一个方法在pre_install中进行hook设置static_framework
4. 静态库framework里面的构成
我们打开手动制作的静态库framework,里面的文件有:
文件夹 | 内容 |
---|---|
framework静态库 |
|
Modules |
5. Modules
苹果推出Modules
主要是为了支持模块化编程,提供更清晰的代码组织和更好的命名空间管理。Modules文件夹里放着.modulemap
和.swiftmodule
文件。.modulemap
是用于C\OC,.swiftmodule
用于swift。
有了Modules后,对于OC库、Swift库、OC+Swift混编的库,外部使用时都可以导入模块,不需要导入头文件,使得开发更加方便。
@import WebKit.WebKitLegacy; //in Objective-C
import WebKit.WebKitLegacy //in Swift
细节可以阅读:开发进阶-Module与Swift库
6. 制作静态库注意点
6.1 静态库符号冲突
符号冲突指的是在OC/C的静态库中,全局变量、静态变量或函数名如果与应用程序或其他静态库中的全局符号相同,导致冲突发生符号重复定义错误. (亲测,如果多个静态库中有相同名称的OC分类,不会导致符号冲突,对于相同名称分类中相同方法的调用,最终调用的方法实现是较晚编译的库中方法。)
使用Swift库一般不会产生符号冲突,Swift 引入了模块化编程的概念,每个 模块拥有自己的命名空间,不同模块中相同名称的符号不会发生冲突。
6.2 静态库中OC分类中的方法找不到运行时奔溃
场景
假设一个静态库,库中有OC写的分类,但分类所属的类定义不在库中如NSString
。
在把这个静态库集成到工程里后,如果编译设置other linker flags
没有添加-ObjC
,那么在使用这个OC分类的方法时,就会在运行时奔溃: unrecognized selector sent to class ..
原因
由于标准UNIX静态库的实现、链接器和Objective-C的动态特性之间的问题,出现了“选择器未识别”运行时异常。Objective-C并没有为每个函数(或方法,在Objective-C中)定义链接器符号,而是只为每个类生成链接器符号。如果使用类别扩展预先存在的类,则链接器不知道如何将核心类实现的对象代码与类别实现相关联。这样可以防止在生成的应用程序中创建的对象响应类别中定义的选择器。
这也就是说,静态库中的OC分类的方法没有与类关联起来。
改法
如果是手动集成这种静态库,需要在主工程中的other linker flags
添加-ObjC
。
-ObjC参数的作用:
This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.
此标志使链接器加载库中定义Objective-C类或类别的每个对象文件。虽然此选项通常会导致更大的可执行文件(由于应用程序中加载了额外的对象代码),但它将允许成功创建有效的Objective-C静态库,其中包含现有类的类别。
有兴趣可以了解具体原因 iOS静态库中类的分类问题和符号冲突问题。
另外,如果使用的库是动态库,也不会有这个问题,原因是动态库是运行时才链接的。
动态库是在运行时被动态加载到内存中的,而不是在编译时被静态链接。这意味着在动态库加载时,它的所有代码才会被加入到进程的地址空间中。相比之下,静态库在编译时就会被链接到可执行文件中,可能会引起加载的时机问题。
CocoaPods集成静态库没有这个问题
如果是使用CocoaPods集成静态库,那么会自动给工程添加编译参数-ObjC
, 所以使用CocoaPods集成的静态库中的OC分类不会有这个问题。
Pods-工程名.debug.xcconfig中
OTHER_LDFLAGS = $(inherited) -ObjC -framework "AFNetworking"
四、了解动态库
1. iOS中的动态库
系统提供的framework都是动态库类型,比如UIKit.framework
、libc++.tbd
;在集成库时,如果是静态库类型,那么静态库内容最终是在主二进制中;如果是动态库,会放在ipa
中的Frameworks
目录。
如果是cocoapods
集成库,那么如果在库的spec中没有指定s.static_framework = true
时,在podfile以下写法都是生成动态库:
方法一:
use_frameworks!
方法二:
use_frameworks! :linkage => :dynamic #使用动态链接
2. 动态库的格式介绍
.tbd
(Text-Based Stub Library)
.tbd 文件是一种文本格式的库文件描述符,主要包含了库的元数据信息,如符号列表、版本信息等。
在 iOS 中,.tbd 文件通常用于描述系统框架和库,例如 iOS SDK 提供的框架。
这些文件并不包含实际的二进制代码,而是提供了一个轻量级的描述,用于编译和链接时确定库的接口和依赖关系。
在 Xcode 构建过程中,.tbd 文件会被用于生成实际的动态库链接信息。
.dylib
.dylib 文件是实际的动态库文件,包含了编译好的二进制代码、数据等。
在 iOS 中,.dylib 文件用于存储动态链接库的实现,可以由系统或第三方提供。
这些文件是真正的共享库,运行时动态加载到应用程序中,提供所需的功能。
.framework
目录结构是规范化,是一种更为结构化的库格式,用于更方便地组织和使用共享代码和资源。
XCFramework
引入了对多平台和多架构的支持,可以包含适用于不同平台和处理器架构的二进制版本。类似于胖二进制。
系统动态库
各种格式都有。从 Xcode7 在导入系统动态库时,可以发现.dylib
文件变成了.tbd
文件。.tbd
文件相比.dylib
文件来说包大小更小,实际使用的还是dylib的二进制代码库。
stackoverflow的回答
比如: libsqlite3.tbd
是个文本文件,其安装名是libsqlite3.dylib
.
archs: [ armv7, armv7s, arm64 ]
platform: ios
install-name: /usr/lib/libsqlite3.dylib
current-version: 216.4
compatibility-version: 9.0
exports:
- archs: [ armv7, armv7s, arm64 ]
symbols: [ __sqlite3_lockstate, __sqlite3_purgeEligiblePagerCacheMemory,
__sqlite3_system_busy_handler, __sqlite_auto_profile,
...
So it appears that the .dylib file is the actual library of binary code that your project is using and is located in the /usr/lib/ directory on the user's device. The .tbd file, on the other hand, is just a text file that is included in your project and serves as a link to the required .dylib binary. Since this text file is much smaller than the binary library, it makes the SDK's download size smaller.
二方库
我们创建的动态库一般是framework和xcframework格式。
3. 动态库的链接
Static frameworks are linked at compile time. Dynamic frameworks are linked at runtime
库类型 | 链接时期截图 |
---|---|
静态库 | |
动态库 |
3. 动态库的优缺点
系统的动态库是各个APP可以共享一份的,这样能节省内存。自己生成的动态库仅限于自己APP内部共享,即和扩展、插件等进程共享。
动态库也可支持设置启动时不加载,在实际用到的时使用dlopen
加载;
//www.greatytc.com/p/08b0cb296278
缺点:
动态库相比做成静态库,最终的ipa包体积会更大一点点;启动时,动态库需要独立加载并动态链接符号,所以启动耗时多。
五、XCFramework
XCFramework 是苹果新出的库类型,在 Xcode 11 及 cocoapods 1.9 以上版本被支持,与普通动态库/静态库最大的区别是将多个平台(iOS, macOS, tvOS, watchOS, iPadOS, carPlayOS,模拟器)的二进制库,捆绑到一个可分发的.xcframework捆绑包中,支持所有的苹果平台和架构。
对比使用 .framework 格式,使用 .xcframework 格式 APP 包大小和启动速度都有提升。
相关资料:
苹果关于Mach-O的说明文档:
https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html#//apple_ref/doc/uid/TP40001827-SW1
苹果关于动态库的说明文档:
https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/OverviewOfDynamicLibraries.html#//apple_ref/doc/uid/TP40001873-SW1
苹果关于静态库OC分类时的文档:
Building Objective-C static libraries with categories:
网友总结文档:
XCFramework 基础-用脚本生成
iOS静态库中类的分类问题和符号冲突问题
iOS之深入解析静态库和动态库