主要来探索一下在Xcode项目工程中,动态库链接动态库,动态库链接静态库,静态库链接静态库,静态库链接动态库的原理。首先我们先介绍下如果使用xcconfig来链接动态库。
使用xcconfig链接动态库
通常我们引入第三方框架可以使用cocoapods来引入,今天我们不使用cocoapods来引入,我们使用xcconfig来配置链接我们想要链接的库。
xcconfig文件其实就是xcode里的config文件,本质是一个用来保存Build Settings键值对的纯文本文件。这些键值对覆盖Build Settings中的值,所以当在xcconfig文件中配置了的选项,在Build Settings中设置将失效。
如何使用呢?我们先新建一个xcconfig文件,并在Project中,将Debug模式指向该xcconfig
我们将提前下载好的动态库
SYTimer.framework放到我们项目的根目录下,
打开 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的含义可以参考动态库原理一文。
我们再次运行程序,运行成功了
这样我们就使用xcconfig完成了动态库的链接。在我们的Xcode的Build Settings已经写入了我们在xcconfig里面设置的值,如下图所示:
。
动态库链接动态库
接下来,我们来模拟动态库里面使用了其他动态库的场景。
正向引用 App->动态库->动态库
我们新建一个framework工程,将其Mach-O Type设置为 Dynamic Library
我们借助
cocoapods,在podfile使用use_frameworks!引入AFNetworking动态库
target 'LYNetworkTool' do
use_frameworks!
pod 'AFNetworking'
end
我们在新建LYAFNetworkTool类,并使用AFNeworking
然后,在
LGNetworkManagerTests.m里面使用该方法
按下 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文件
其Mach-O中确实没有AFNetworking文件。
所以,我们需要将 AFNetworking拷贝到 LYNetworkToolTests的目录下,或者在其工程里面引入AFNetworking,此时Podfile的内容如下
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).
我们看一下其包内容:
反向依赖 App<->动态库->动态库
所谓反向依赖就是动态库使用主工程里面的代码,我们在LYNetworkToolTests工程中,
1,新建LYAppObject类,并在动态库里面使用。
2,设置LYNetworkTool的header search path:
3,commnad + B进行编译,编译通过后,在动态库中使用 LYAppObject对象
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,在LYNetworkTool的xcconfig文件中指定_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"
静态库链接静态库
我们将LYNetworkTool的Mach-O Type设置为Static Library,那么此时该库为静态库,我们引用静态库AFNetworking,此时command + U运行,我们看到此时找不到_OBJC_CLASS_$_AFNetworkReachabilityManager符号,
为什么呢?
因为此时我们的链接关系为 App <-> 静态LYNetworkTool->静态库AFNetworking,我们需要将 AFNetwoking的路径告诉App,让App去加载
在LYNetworkToolTests.xctest的Target的Build 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,有兴趣的可以下载玩一下。