前言:之前对于静态库和动态库的了解太过于片面,网上的文章大都是大佬们的理解,这里重新梳理一下,方便日后翻阅。
1. 库
库(Library
)其实就是一段编译好的二进制代码
,可以被操作系统载入内存执行。
1.1 应用场景
- 需要将代码给别人使用,但不想别人看到源码,就需要将代码打包成库,只暴露出头文件(比如经常使用的一些三方库)
- 对于不会进行大改动的模块,可以打包成库,可以减少编译的时间,库已经是编译好的二进制了,编译的时候只需要
Link
一下,不会浪费编译时间。
1.2 概念介绍
上面在应用场景中提到了Link
(链接),Link
又区分为静态链接
和动态链接
,所以便有了静态库
和动态库
的概念。
1.2.1 framework
framework
其实是一种打包方式,它既可以是动态库也可以是静态库(系统提供的framework
是动态库,我们自己做的一般是静态的,苹果不允许我们使用自己做的动态库
)。它只是将二进制文件、头文件和资源文件打包到了一起,方便管理和分发,和静态库&动态库的本质没有什么关系
。
1.2.2 静态库
静态库
(静态链接库)文件以.a
或者.framework
结尾,之所以叫静态库,是因为在编译的时候它会被复制一份到可执行文件
中,多次使用就有多份拷贝,程序运行时将不再需要改静态库。
优点:
目标程序没有外部依赖,直接就可以运行;缺点:
会增大目标程序的体积;
.a 和 .framework有什么区别?
.a是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件;
.a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用;
.a + 头文件 + 资源文件 = .framework。
1.2.3 动态库
动态库
(动态链接库) 以.dylib
或者.framework
后缀结尾,它和静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。程序运行时,动态库才会被真正加载进来。
优点:
不影响目标程序体积;
可以被多个程序共享,多个程序都可以动态链接到同一个动态库;缺点:
运行时才使用,影响启动速度(APP 启动时会链接动态库,数量多了之后耗费时间);
程序依赖于外部环境,如果环境缺少动态库或者库的版本不正确,就会导致程序无法运行;
1.2.4 iOS系统下的framework
iOS系统下的 framework
可以分为 3 类:
-
- 系统级别的动态库:
Dynamic Framework
Dynamic Framework
,动态库,系统提供的framework
都是动态库,比如UIKit.framework
,具有所有动态库的特性:在程序运行时由系统加载到内存,系统只加载一次,比如抖音和微信的可执行文件加载动态库时,内存中只有一份动态库 A:
- 系统级别的动态库:
-
- 用户级别的动态库:
Embedded Framework
Embedded Framework
,这个是用户可以制作的“动态库”,它是受到 iOS 平台限制(签名机制和沙盒机制限制)的动态库,它具有部分动态特性,比如:Embedded Framework
可以在Extension
可执行文件 和APP
可执行文件 之间共享,但是不能像系统的动态库一样,在不同的APP
(进程) 中共享。
Embedded Framework
最后也还是要拷贝到应用程序
中,但是与静态库不同,它不在可执行文件中
:
- 用户级别的动态库:
本质上讲,
Embedded Framework
是动态库,只是我们给动态库起的一个别名!
-
- 用户级别的静态库:
Static Framework
Static Framework
,静态库,用户可以制作,可以粗略的理解为,它等价于 头文件 + 资源文件 + 二进制代码,它具有静态库的属性。
- 用户级别的静态库:
如下图,抖音和微信的可执行文件中都会有这个静态库A:
2. 创建 Framework
下图是Xcode12 提供的创建 framework
的模板:
我们可以使用者两个模块,分别创建一个动态的framework
和一个静态的 framework
。
2.1 制作静态库
2.1.1 制作.a静态库
我们需要创建一个工程,选择为Static Library
模板,工程项目名为:TestStaticLibrary
,如下图:
我们可以看到项目中有一个和项目同名的类,Product
下有一个红色的.a
文件,说它目前是不存在的,如下:
在和项目同名类TestStaticLibrary
里加上一个实例方法,如下:
@interface TestStaticLibrary : NSObject
- (void)log;
@end
@implementation TestStaticLibrary
- (void)log{
NSLog(@"Hello TestStaticLibrary!!!");
}
@end
我们在模拟器下编译
一下这个.a
文件,可以看到.a
文件在Debug-iphonesimulator
目录下:
我们再新建一个工程:TestApp
,然后把上面生成的.a
文件添加到TestApp
项目中,同时也需要添加.a
的头文件TestStaticLibrary.h
,然后就可以使用了:
2.1.2 制作 framework
静态库
同样需要创建一个工程,选择为 Framework
模板,工程项目名为:TestStaticFramework
,如下图:
可以看到 Products
下刚好就是.framework
了,并且有一个和项目同名的头文件TestStaticFramework.h
,如下图:
创建的其实默认是动态库,我们需要修改一下:Build Settings => Mach-O Type
改为Static Library
,如下图:
关于
Mach-O
文件的理解,可以查看Mach-O文件介绍这篇文章。
我们创建一个Student
类,里面也只有一个实例方法:
@interface Student : NSObject
- (void)log;
@end
@implementation Student
- (void)log{
NSLog(@"Hello TestStaticFramework Student!!!");
}
@end
我们在TestStaticFramework.h
头文件中引入刚创建的Student.h
头文件,并且在下面这个配置里将 Student.h
文件,从 Project
中拖入到Public
下面暴露出来,如下:
公开的头文件中,
#import
的其他类也要公开,添加到public
中,如果不想公开,就在头文件用用@class
的方式,在对应的.m
中用#import
方式。
然后选择模拟器进行编译,之后将Products
下的TestStaticFramework.framework
添加到上面创建的TestApp
项目中,导入头文件直接使用就可以了,如下图:
静态库的使用比较简单,直接将
framework
添加到项目中即可。
2.2 制作 Embedded Framework
我们只能制作.framework
这样的动态库,制作过程和上面.framework
静态库的过程一样,只不过系统默认设置的就是动态类型(Mach-O Type
默认为Dynamic Library
),这次就不需要修改了。
我们创建一个 Teacher
类,里面也有一个log
方法,之后暴露头文件的方法和上面.framework
静态库一样,这里就不多说了。
我们将在模拟器下编译的好的 TestDynamicFramework.framework
文件导入 TestApp
项目中,编译之后,没问题。
现在,我们引入头文件,调用 Teacher
的实例方法,运行之后,就崩溃了,如下图:
解决方案:
我们需要在项目Target
的 General -> Frameworks,Libraries, and Embedded Content
中将TestDynamicFramework.framework
从 Do Not Embed
修改为Embed & Sign
之后才能正常使用,如下图:
Embed:嵌入,用于动态库,动态库在运行时链接,所以它们需要被打进.app里面。
Signing:签名,用于动态库,如果已经有签名了就不需要再签名,codesign -dv framework链接
,如果返回code object is not signed at all
或者adhoc
,选择Embed&Sign,否则选择 Embed Without Signing
3. 动态库和静态库的差异
3.1 Xcode 配置
使用静态库的时候直接将.framework
导入就可以使用了。
在使用动态库的时候,需要对.framework
进行Embed&Singing
设置。
3.2 在 .app
包中的表现
经过上面的步骤,我们的 TestApp
项目已经引入了libTestStaticLibrary.a
静态库 TestStaticFramework.framework
静态库 和 TestDynamicFramework.framework
动态库,现在我们在TestApp.app
文件中查看下有什么不同:
可以看到TestDynamicFramework.framework
动态库被复制到了.app文件中的Frameworks 文件夹
下,另外我们用 MachOView
工具打开TestApp可执行文件
,在 Load Commands 下
可以看到有LC_LOAD_DYLIB(TestDynamicFramework.framework)
,如下图:
从上面图中我们看到,TestDynamicFramework.framework
动态库存放在.app
中了,那另外两个静态库呢?
静态库在编译的时候其实就被链接到TestApp.app可执行文件
中了,如下图:
4. 不同架构的库
上面编译静态库或者动态库都是在模拟器
下编译的,它只支持当前选择设备的架构,我们可以使用 libo -info
命令查看一下,以.framework
静态库为例,如下,只支持x86_64
:
demo@Pro ~ % lipo -info TestStaticFramework.framework/TestStaticFramework
Non-fat file: TestStaticFramework.framework/TestStaticFramework is architecture: x86_64
这样的静态库如果用于真机的话,肯定会报错的,所以我们需要生成支持模拟器和真机的静态库,首先需要修改配置:Build Active Architecture Only
改为NO
:
修改TestStaticFramework
的Build
配置为release
,如下图:
选择Generic iOS Device
和任意一个模拟器
各编译一次,然后Show in Finder
,如下图:
可以看到真机和模拟器都生成了.framwork
,分别在Release-iphoneos
下和Release-iphonesimulator
下,可以用命令lipo -info 二进制文件
,看下支持的iOS
的CPU架构
:
-
Release-iphoneos
里面支持armv7、arm64
,属于真机,用到模拟器就会报错 -
Release-iphonesimulator
里面支持i386、x86_64
,属于模拟器,用到真机会报错
我们肯定不能用两个静态库啊,我们可以使用lipo
命令合并这两个静态库,如下:
lipo -create 二进制文件1 二进制文件2 -output 最终的可执行文件路径
将合成的二进制文件替换掉原来的,然后把这个新的framework
拖进项目就可以使用了。
合成这一步可以使用脚本来完成,另外,如果静态库中有
Category
类,就要在使用静态库项目的配置中添加-ObjC
或者-all_load
。
目前先记录这么多,等再遇到相关问题,持续更新。