iOS动态库与静态库的配置与使用

2023-06-28  本文已影响0人  张聪_2048

一、静态库和动态库依赖问题

1.1、两个库相关的区别

在构建的过程中: 动态库需要经过静态链接。这里你没有看错,动态库的生成需要静态链接。而静态库的生成,不需要经过静态链接,仅仅只是简单的将对应的 .o 文件压缩。所以这里也可以通过命令行工具将 .o 文件重新解压缩出来。 这里我们重点说一下动态库,动态库和我们项目产出的主工程可执行文件对比,其编译、链接等过程是完全一样的。换句话说,动态库是一个没有 main 函数的可执行文件。

在使用中: 动态库是在程序启动运行时,被动态链接后执行调用的。而静态库则参与程序的静态链接,被链入主工程的二进制可执行文件中。这也就是为什么,动态库需要被拷贝内嵌 (embed) 到包内,静态库不需要的原因。

简单说下静态链接:将多个目标文件合并成一个可执行文件。在这个过程中,把多个目标文件里面相同性质的段合并到一起。静态链接(static linking)是程序构建中的一个重要环节,它负责分析 compiler 等模块输出的 .o、.a、.dylib 、经过对 symbol 的解析、重定向、聚合,组装出 executable 供运行时 loader 和 dynamic linker 来执行,有着承上启下的作用。

动态库和静态库,在生成时,因为其是否经过静态链接,产生了差异。动态库经过静态链接后,会经过符号决议、重定位等流程,会将依赖的静态库链接进来,也就是说,动态库会吸附静态库。如果依赖的是动态库,则走动态链接的流程。

在使用时,因为动态库只需要动态链接,所以不会在主工程编译阶段报错,但可能在运行阶段报找不到库。静态库则需要被主工程静态链接,所以当缺少符号或者符号重复冲突时,会在编译阶段报错。

1.2、动静相互依赖问题

两个静态库有相同符号:

静态库 A 依赖静态库 B:

// 静态库A
@interface ObjA : NSObject
+ (void)test;
@end

@implementation ObjA
+ (void)test {
    NSLog(@"ObjA Test");
    [ObjB test]; //依赖 B 库中的类方法
}
@end
  
// 静态库B
@interface ObjB : NSObject
+ (void)test;
@end

@implementation ObjB
+ (void)test {
    NSLog(@"ObjB Test");
}
@end
  
// 主工程中调用 [ObjA test];

两个动态库包含相同的符号:

动态库 A 依赖动态库 B:

静态库和动态库包含相同符号:

静态库依赖动态库:

动态库依赖静态库:

二、一些参数配置与区别

可下载 Demo

2.1、引入静态库的参数配置说明

2.1.1、查看指令

通过ar -t命令可以列出静态库的所有.o文件,通过命令nm -p查看静态库的符号信息,如下图所示,可以看出静态库实际上是目标文件(.o文件)的集合,它的符号是以.o文件为单位分开的。

图1:查看静态库指令介绍.png

这里我们新建一个ZJHAppDemo2工程,然后将静态库导入进入。运行成功后,通过objdump --macho -t 命令查看主程序的符号信息,如下图所示,静态库链接到主程序之后它的符号变成了本地符号,实际上是跟主程序app合并在一起了。

图2:静态库链接到主程序app.png

2.1.2、-noall_load

xcode 的build默认是-noall_load,-noall_load顾名思义就是不会所有符号的加载,而是链接器链接一个静态库之前去扫描静态库文件,找到需要的代码再进行链接。例如ZJHStaticNoUsedTool类没有被用到,就不会被链接。

2.1.3、-all_load

链接所有符号,不管代码有没有使用,比如上面的例子,即使不用是ZJHStaticNoUsedTool也会被链接到app中:

图3:-all_load的使用.png

2.1.4、-force_load

使用 -force_load,这个你可以指定要载入所有方法的库,后面必须跟一个只想静态库的路径。比如我们在创建一个静态库ZJHStaticSDK2,然后创建相同ZJHStaticPublicTool类,之后在ZJHAppDemo2工程中引用两个库,这时第二个导入的libZJHStaticSDK2.a,默认会覆盖第一个。

图4:-force_load的使用.png

如果我们需要选择第一个的话,可以使用 -force_load $(SRCROOT)/ZJHAppDemo2/libZJHStaticSDK.a,意思要载入libZJHStaticSDK.a。

2.1.5、-ObjC

这个flag告诉链接器把库中定义的Objective-C类和Category都加载进来,这样编译之后的app会变大(因为加载了其他的objc代码进来)。由于OC语言符号链接的基本单位是类,静态库链接时首先会链接本类,而Category是运行时才会被加载的,因此会被静态链接器直接忽略掉,通过-ObjC命令是告知链接器链接所有的OC代码。比如我们实现ZJHStaticPublicTool+Category,之后在App中引用这个category,然后运行会报错,如下图:

图5:-ObjC配置使用1.png

添加-ObjC命令之后可以正常调用,Category已被链接到主程序,如下图:

图6:-ObjC配置使用2.png

2.1.6、dead code striping

dead code strip的作用(Remove functions and data that are unreacheble by entry point or export symbols)不管是.o文件、静态库还是动态库,未被使用代码会被剥离。没有被使用的代码,就是dead code 。xcode的默认情况下是会剥离dead code的。

前面提到的链接指令-noall_load、 -all_load、-force_load和-ObjC都是针对静态库的,跟dead code strip没有任何关系。dead code strip是针对的是.o文件、静态库和动态库。

2.2、@executable_path、@loader_path、@rpath

2.2.1 @executable_path

这个变量表示可执行程序所在的目录. 比如 /path/QQ.app/Contents/MacOS/

2.2.2 @loader_path

这个变量表示每一个被加载的 binary (包括App, dylib, framework, plugin等) 所在的目录,对于framework内置模块或plugin特别适合。在一个程序中, 对于每一个模块, @loader_path 会解析成不用的路径, 而 @executable_path 总是被解析为同一个路径(可执行程序所在目录).

比如一个会被多个程序调用的 plugin,位于 /path/Flash Player.plugin/Contents/MacOS/Flash Player,依赖 /path/Flash Player.plugin/Contents/Frameworks/XPSSO.dylib,那么 XPSSO.dylib 的 INSTALL_PATH 可以设置为 @loader_path/../Frameworks, 这样设置的话, 不论 Flash Player.plugin 目录放到什么位置, XPSSO.dylib 都能正确的被加载.

2.2.2 @rpath

@rpath 和前面两个不同,它只是一个保存着一个或多个路径的变量。比如 XPSSO.dylib 被两个.app 使用,且被包含的路径不同。

  1. 对于被当成第三方库使用的dylib或Framework,本身Install name可以设置为包含@rpath的值,这个@rpath其实是一个变量
  2. 对于动态库的使用者,可以通过设置Runpath Search Paths指定多个值,这些值在运行时会用于替代动态库自己设置的@rpath来查找动态库

比如:
softA.app/Contents/MacOS/dylib/XPSSO.dylib
softB.app/Contents/MacOS/Frameworks/XPSSO.dylib

将 XPSSO.dylib 的 INSTALL_PATH 设置成 @loader_path/../dylib 或 @loader_path/../Frameworks 都只能满足其中一个 .app 的需求。

要解决这个问题,就可以用 @rpath,将 XPSSO.dylib 的 INSTALL_PATH 设置成 @rpath,然后在编译 softA.app, softB.app 时分别指定 @rpath 为 @loader_path/../dylib, @loader_path/../Frameworks,问题得到了解决。

@rpath 的另一个优点是可以设置多个路径。如果 softA.app 还需要使用另一个 .plugin (假设它的 INSTALL_PATH 也设置成了 @rpath), 位于 @loader_path/../plugin, 把这个路径加到 @rpath 即可。

三、组件化中引用库

3.1、组件引用三方库

打开配置文件 - 组件名字.podspec,配置组件frameworks依赖,s.vendored_frameworks: 包含的.framework,多个用逗号隔开。例如:

  s.vendored_frameworks = [
      'private/AFNetworking/AFNetworking.framework',
      'private/BeeHive/BeeHive.framework',
      'private/Masonry/Masonry.framework',
      'private/YYModel/YYModel.framework'
  ]

然后 s.vendored_libraries:,可以引用.a文件,使用方式和s.vendored_frameworks相同

3.2、动态库和静态库的设置

use_frameworks!常用的形式:

use_frameworks! :linkage => :static  # 将引入的源码组件打包成静态库。只对源码组件有效
use_frameworks! :linkage => :dynamic # 将引入的源码组件打包成动态库。只对源码组件有效
use_frameworks!     # 根据 pod 类型来决定应该打包成静态库还是动态库。
# use_frameworks!   # 不使用

使用 use_frameworks! 时,如果没有指定源码库打包类型,则会根据对应组件的 podspec 文件中的设置来决定。设置字段如下:s.static_framework = true/false,设置true代表为静态库,设置false代码为动态库。

3.3、批量设置动静库

podfile文件添加以下代码,可以批量的设置组件引用的库为动态库还是静态库,dynamic_frameworks数组里面的都是动态库,不包含的还是默认静态库

dynamic_frameworks = Array['AFNetworking','MJExtension', 'YYCache']

pre_install do |installer|
  # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289
  Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {}
  
  #把第三方库改成静态库
  installer.pod_targets.each do |pod|
    if !dynamic_frameworks.include?(pod.name)
      #puts "Overriding the static_framework? method for #{pod.name}"
      #注意cocoapods 1.7.3以下是static_framework
      def pod.build_as_static_framework?;
         true
      end
      def pod.build_type;
        if Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.9.0')
           BuildType.static_framework
        else
           Pod::Target::BuildType.static_framework
        end
      end
      def pod.static_framework?;
         true
      end
    end
  end
end

上一篇下一篇

猜你喜欢

热点阅读