使用Xcode进行动态库和静态库之间的链接

2,151 阅读4分钟

主要来探索一下在Xcode项目工程中,动态库链接动态库,动态库链接静态库,静态库链接静态库,静态库链接动态库的原理。首先我们先介绍下如果使用xcconfig来链接动态库。

使用xcconfig链接动态库

通常我们引入第三方框架可以使用cocoapods来引入,今天我们不使用cocoapods来引入,我们使用xcconfig来配置链接我们想要链接的库。

xcconfig文件其实就是xcode里的config文件,本质是一个用来保存Build Settings键值对的纯文本文件。这些键值对覆盖Build Settings中的值,所以当在xcconfig文件中配置了的选项,在Build Settings中设置将失效。

如何使用呢?我们先新建一个xcconfig文件,并在Project中,将Debug模式指向该xcconfig

引入xcconfig.jpg 我们将提前下载好的动态库SYTimer.framework放到我们项目的根目录下,

目录结构.png

打开 xcconfig文件,输入一下参数,指定链接三要素

// 1
HEADER_SEARCH_PATHS =$(inherited)${SRCROOT}/SYTimer.framework/Headers
// 2
FRAMEWORK_SEARCH_PATHS = $(inherited) ${SRCROOT}
// 3
OTHER_LDFLAGS = $(inherited) -framework "SYTimer"
  • 1,相当于ld-I参数,指定头文件路径。
  • 2,相当于ld-F参数,指定framework的路径。
  • 3,相当于ld-framework参数,指定framework的名称

接下来,我们在ViewController.m中就可以使用 SYTimer

#import "ViewController.h"
#import <SYTimer.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    SYTimer *timer = [[SYTimer alloc] init];
    
    NSLog(@"%@",timer);
}
@end

编译成功,然后我们运行工程,运行的时候报了dyld: Library not loaded: @rpath/SYTimer.framework/SYTimer 错了,根据这个@rpath/SYTimer.framework/SYTimer查找动态库,没有找到该动态库,此时,我们需要指定@rpath

// 1
HEADER_SEARCH_PATHS =$(inherited)${SRCROOT}/SYTimer.framework/Headers
// 2
FRAMEWORK_SEARCH_PATHS = $(inherited) ${SRCROOT}
// 3
OTHER_LDFLAGS = $(inherited) -framework "SYTimer"
// 4
LD_RUNPATH_SEARCH_PATHS = $(inherited) ${SRCROOT}

关于@rpath的含义可以参考动态库原理一文。

我们再次运行程序,运行成功了 运行成功.png

这样我们就使用xcconfig完成了动态库的链接。在我们的XcodeBuild Settings已经写入了我们在xcconfig里面设置的值,如下图所示:

header_search_path.png

动态库链接动态库

接下来,我们来模拟动态库里面使用了其他动态库的场景。

正向引用 App->动态库->动态库

我们新建一个framework工程,将其Mach-O Type设置为 Dynamic Library

framwork动态库.png 我们借助cocoapods,在podfile使用use_frameworks!引入AFNetworking动态库

target 'LYNetworkTool' do

  use_frameworks!
  pod 'AFNetworking' 	
	
end

我们在新建LYAFNetworkTool类,并使用AFNeworking

使用AFNetwork.png 然后,在 LGNetworkManagerTests.m里面使用该方法

LGNetworkManagerTests.png

按下 command + U运行我们的LYNetworkToolTests工程,结果我们测试没通过 (/Users/bel/Library/Developer/Xcode/DerivedData/LYNetworkTool-dbjrztcvjbgtdaeqfvzfkyhqeagn/Build/Products/Debug-iphonesimulator/LYNetworkToolTests.xctest/LYNetworkToolTests): Library not loaded: @rpath/AFNetworking.framework/AFNetworking,在LYNetworkToolTests里面没有找到 AFNetworking,我们来查看其Mach-O文件 Macho文件.png

其Mach-O中确实没有AFNetworking文件。 所以,我们需要将 AFNetworking拷贝到 LYNetworkToolTests的目录下,或者在其工程里面引入AFNetworking,此时Podfile的内容如下

Podfile.png pod install成功后,我们运行程序command + U,此时运行就成功了

Test Suite 'LYNetworkToolTests.xctest' started at 2021-07-05 23:09:34.469
Test Suite 'LYNetworkToolTests' started at 2021-07-05 23:09:34.469
Test Case '-[LYNetworkToolTests testExample]' started.
2021-07-05 23:09:34.471572+0800 xctest[47348:3574307] -----AFNetworkMangager----<AFNetworkReachabilityManager: 0x7fe97d41a560>
2021-07-05 23:09:34.471835+0800 xctest[47348:3574307] ---- shared ------ <LYAFNetworkTool: 0x7fe97d7f3ac0>
Test Case '-[LYNetworkToolTests testExample]' passed (0.002 seconds).

我们看一下其包内容:

包内容2.png

反向依赖 App<->动态库->动态库

所谓反向依赖就是动态库使用主工程里面的代码,我们在LYNetworkToolTests工程中, 1,新建LYAppObject类,并在动态库里面使用。

AppSayHello.png

2,设置LYNetworkToolheader search path:

header_search_path设置.jpg

3,commnad + B进行编译,编译通过后,在动态库中使用 LYAppObject对象

使用LYAppObject.png

4,切换到 LYNetworkToolTestsscheme,然后 command + U运行LYNetworkToolTests工程

最终运行失败了,Undefined symbols for architecture x86_64: "_OBJC_CLASS_$_LYAppObject", referenced from: objc-class-ref in LYAFNetworkTool.o,原因是没有找到 _OBJC_CLASS_$_LYAppObject符号,因为LYAppObject符号在App里面,动态库里面的符号表里面没有,我们需要将告诉编译器该符号是动态查找的符号

5,在LYNetworkToolxcconfig文件中指定_OBJC_CLASS_$_LYAppObject 为动态查找的符号,使用 -Xlinker -U -Xlinker _OBJC_CLASS_$_LYAppObject

OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -Xlinker -U -Xlinker _OBJC_CLASS_$_LYAppObject

这样就运行成功了,这样我们就是实现了在动态库中,使用主App中的类和方法。

Test Suite 'LYNetworkToolTests.xctest' started at 2021-07-05 23:57:42.228
Test Suite 'LYNetworkToolTests' started at 2021-07-05 23:57:42.228
Test Case '-[LYNetworkToolTests testExample]' started.
2021-07-05 23:57:42.230076+0800 xctest[47602:3609773] -----AFNetworkMangager----<AFNetworkReachabilityManager: 0x7f86467765c0>
2021-07-05 23:57:42.232087+0800 xctest[47602:3609773] App主工程--- -[LYAppObject sayAppHello]
2021-07-05 23:57:42.232557+0800 xctest[47602:3609773] ------ LYAppObject ------ <LYAppObject: 0x7f8646772740>
2021-07-05 23:57:42.232959+0800 xctest[47602:3609773] ---- shared ------ <LYAFNetworkTool: 0x7f864677ce20>

动态库链接静态库

我们将 use_frameworks注释掉,然后pod install后,引入的是 libAFNetworking.a,此时,引入的静态库,我们需要重新将_OBJC_CLASS_$_LYAppObject符号设置为动态查找的符号,然后我们直接运行command + U,此时能够正常运行成功。 此时,AFNetworking静态库并不在LYNetworkToolTests.xctest目录中,所以我们不需要将 AFNetworking拷贝到Frameworks目录下,在xcconfig文件中,我们也可以看出来,静态库存放的位置为

LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking"

静态库链接静态库

我们将LYNetworkToolMach-O Type设置为Static Library,那么此时该库为静态库,我们引用静态库AFNetworking,此时command + U运行,我们看到此时找不到_OBJC_CLASS_$_AFNetworkReachabilityManager符号,

为什么呢?

因为此时我们的链接关系为 App <-> 静态LYNetworkTool->静态库AFNetworking,我们需要将 AFNetwoking的路径告诉App,让App去加载 在LYNetworkToolTests.xctestTargetBuild Setting中设置

OtherLinker Flags:  -lAFNetworking
Library Search Paths: 
${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking

这样就可以运行成功了,和动态库链接动态库,动态库链接静态库 不同的是,不需要任何额外设置就可以反向依赖主工程。

静态库链接动态库

我们 use_framework!打开,安装AFN动态库,此时我们直接运行Appcommand + U,编译报错了 Undefined symbols for architecture x86_64: "_OBJC_CLASS_$_AFNetworkReachabilityManager找到AFNetworking相关的符号。

为什么呢?

App <-> 静态库 -> 动态库,静态库的代码会放在App的里面,就相当于 我们的App直接去链接动态库,但是我们在App里面还没有设置链接参数-F-framework, 我们在LYNetworkToolTests.xctest的target的Build Setting中,设置这两个参数 Other Linker Flags = -framework "AFNetworking" Framework Search Paths = ${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/AFNetworking。 这样就能编译通过了。

当我们运行时,我们又碰到了 动态库链接动态库里面的错误,AFNetworking找不到,解决办法和其一致。

总结

在这篇文章里,我们探索了iOS静态库和动态库之间的链接,及一些常见链接配置。本文所有的示例demo,我已经上传至我的Github,有兴趣的可以下载玩一下。