[iOS] 静态库和动态库的理解

前言:之前对于静态库和动态库的了解太过于片面,网上的文章大都是大佬们的理解,这里重新梳理一下,方便日后翻阅。

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 类:

    1. 系统级别的动态库:Dynamic Framework
      Dynamic Framework,动态库,系统提供的framework 都是动态库,比如 UIKit.framework,具有所有动态库的特性:在程序运行时由系统加载到内存,系统只加载一次,比如抖音和微信的可执行文件加载动态库时,内存中只有一份动态库 A:
      image.png
    1. 用户级别的动态库:Embedded Framework
      Embedded Framework,这个是用户可以制作的“动态库”,它是受到 iOS 平台限制(签名机制和沙盒机制限制)的动态库,它具有部分动态特性,比如:Embedded Framework 可以在Extension可执行文件 和APP可执行文件 之间共享,但是不能像系统的动态库一样,在不同的 APP(进程) 中共享。
      Embedded Framework最后也还是要拷贝到应用程序中,但是与静态库不同,它不在可执行文件中
      image.png

本质上讲,Embedded Framework 是动态库,只是我们给动态库起的一个别名!

    1. 用户级别的静态库:Static Framework
      Static Framework,静态库,用户可以制作,可以粗略的理解为,它等价于 头文件 + 资源文件 + 二进制代码,它具有静态库的属性。

如下图,抖音和微信的可执行文件中都会有这个静态库A:


image.png

2. 创建 Framework

下图是Xcode12 提供的创建 framework 的模板:

截屏2021-02-28 下午4.08.25.png

我们可以使用者两个模块,分别创建一个动态的framework 和一个静态的 framework

2.1 制作静态库

2.1.1 制作.a静态库

我们需要创建一个工程,选择为Static Library 模板,工程项目名为:TestStaticLibrary,如下图:

截屏2021-02-28 下午4.38.18.png

我们可以看到项目中有一个和项目同名的类,Product 下有一个红色的.a文件,说它目前是不存在的,如下:

image.png

在和项目同名类TestStaticLibrary里加上一个实例方法,如下:

@interface TestStaticLibrary : NSObject

- (void)log;

@end

@implementation TestStaticLibrary

- (void)log{
    NSLog(@"Hello TestStaticLibrary!!!");
}

@end

我们在模拟器下编译一下这个.a文件,可以看到.a文件在Debug-iphonesimulator目录下:

截屏2021-02-28 下午4.41.44.png

我们再新建一个工程:TestApp,然后把上面生成的.a文件添加到TestApp项目中,同时也需要添加.a的头文件TestStaticLibrary.h,然后就可以使用了:

image.png

2.1.2 制作 framework 静态库

同样需要创建一个工程,选择为 Framework 模板,工程项目名为:TestStaticFramework,如下图:

image.png

可以看到 Products下刚好就是.framework了,并且有一个和项目同名的头文件TestStaticFramework.h,如下图:

image.png

创建的其实默认是动态库,我们需要修改一下:Build Settings => Mach-O Type 改为Static Library,如下图:

截屏2021-02-28 下午5.17.41.png

关于 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下面暴露出来,如下:

image.png

公开的头文件中,#import的其他类也要公开,添加到public中,如果不想公开,就在头文件用用@class的方式,在对应的.m中用#import方式。

然后选择模拟器进行编译,之后将Products下的TestStaticFramework.framework 添加到上面创建的TestApp项目中,导入头文件直接使用就可以了,如下图:

image.png

静态库的使用比较简单,直接将framework 添加到项目中即可。

2.2 制作 Embedded Framework

我们只能制作.framework这样的动态库,制作过程和上面.framework静态库的过程一样,只不过系统默认设置的就是动态类型(Mach-O Type 默认为Dynamic Library),这次就不需要修改了。

我们创建一个 Teacher类,里面也有一个log 方法,之后暴露头文件的方法和上面.framework静态库一样,这里就不多说了。

我们将在模拟器下编译的好的 TestDynamicFramework.framework 文件导入 TestApp 项目中,编译之后,没问题。

现在,我们引入头文件,调用 Teacher的实例方法,运行之后,就崩溃了,如下图:

image.png

解决方案:
我们需要在项目TargetGeneral -> Frameworks,Libraries, and Embedded Content中将TestDynamicFramework.frameworkDo Not Embed 修改为Embed & Sign 之后才能正常使用,如下图:

截屏2021-02-28 下午7.36.04.png

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 文件中查看下有什么不同:

截屏2021-02-28 下午7.47.31.png

可以看到TestDynamicFramework.framework动态库被复制到了.app文件中的Frameworks 文件夹下,另外我们用 MachOView工具打开TestApp可执行文件,在 Load Commands 下可以看到有LC_LOAD_DYLIB(TestDynamicFramework.framework) ,如下图:

image.png

从上面图中我们看到,TestDynamicFramework.framework动态库存放在.app中了,那另外两个静态库呢?
静态库在编译的时候其实就被链接到TestApp.app可执行文件中了,如下图:

截屏2021-02-28 下午9.47.15.png

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:

截屏2021-02-28 下午10.10.09.png

修改TestStaticFrameworkBuild 配置为release,如下图:

截屏2021-02-28 下午10.12.36.png

选择Generic iOS Device任意一个模拟器各编译一次,然后Show in Finder,如下图:

截屏2021-02-28 下午10.14.25.png

可以看到真机和模拟器都生成了.framwork,分别在Release-iphoneos下和Release-iphonesimulator下,可以用命令lipo -info 二进制文件,看下支持的iOSCPU架构

image.png

  • Release-iphoneos里面支持armv7、arm64,属于真机,用到模拟器就会报错
  • Release-iphonesimulator里面支持i386、x86_64,属于模拟器,用到真机会报错

我们肯定不能用两个静态库啊,我们可以使用lipo 命令合并这两个静态库,如下:

lipo -create 二进制文件1 二进制文件2 -output 最终的可执行文件路径

将合成的二进制文件替换掉原来的,然后把这个新的framework拖进项目就可以使用了。

合成这一步可以使用脚本来完成,另外,如果静态库中有Category类,就要在使用静态库项目的配置中添加-ObjC或者-all_load

目前先记录这么多,等再遇到相关问题,持续更新。

你可能感兴趣的:([iOS] 静态库和动态库的理解)