内容简介:相信玩Ok,解决方案是有了,我们需要更深入的理解下:为什么
相信玩 iOS
开发的同学对 tbd
这个格式的文件已经不再陌生了。最近 Xcode 10
升级的时候,你会发现很多原先用 libstdc++
的库在新的 Xcode
已经没有链接通过。而临时的解决方案也比较简单,网上也很多这样的文章,简而言之就是从 Xcode 9
中拷贝对应的 libstdc++.tbd
文件给新的 Xcode 10
来使用。
Ok,解决方案是有了,我们需要更深入的理解下:为什么 拷贝tbd文件,就能够成功解决链接问题?
tbd格式解析
tbd
全称是 text-based stub libraries
,本质上就是一个 YAML 描述的文本文件。
他的作用是用于记录 动态库的一些信息 ,包括导出的符号、动态库的架构信息、动态库的依赖信息。
为什么需要包含这些信息呢?
- 动态库的架构信息是了确保运行的程序在运行的平台加载正确的库。比如你不能在运行
ARM
指令集的iOS
设备上加载x86
格式的库。
后续我们会举一个手动修改 tbd
中 install-name
字段的小例子来让运行在模拟器的时候加载 ARM64
架构的动态库
- 导出的符号。写过程序的人都知道,我们肯定会依赖别人提供的一些函数方法。一般业界都会把这些函数或者方法封装成库的形势。
那库就分为静态库和动态库两种。相信网上关于这两者的讨论和阐述已经很多了,再次不再赘述。唯一需要提及的一点是,动态库是在程序运行(启动依赖或者按需加载)时候加载进程序的地址空间的,那么我们在静态期的时候,是如何得知动态库提供了哪些能力呢? 而这就是 tbd
格式提供的导出符号表的加载,它会指导链接器在链接过程中,将需要决议的符号先做个标记,标记是来自哪个动态库。
这里举个小例子吧。
在程序构建的过程中,比如我们开发一个iOS应用,毋庸置疑的会用到 UIKit
这个动态库。而为了使我们的程序能够构建成功,这里分为了两个步骤:
-
通过引入头文件,
import <UIKit/UIKit.h>
,我们知道了UIKit
里面的函数、变量声明。有声明,就能通过编译器的检查。 -
我们在代码里面使用了
UIKit
的函数,其本质是一种符号,因此需要链接器来决议这个符号来自哪?要是所有地方都找到,就会报类似undefined symbol
之类的错误(想必大家已经很熟悉了)。
为什么要改造成tbd格式
tbd
格式实际上是从 Xcode 7
时代引入的。
用于取代在真机开发过程中直接使用传统的 dylib
用于取代在真机开发过程中直接使用传统的 dylib
用于取代在真机开发过程中直接使用传统的 dylib
我们都知道一个库在没有 strip
诸如调试信息、非导出符号的情况下是非常大的。但是由于在开发过程中,调试等过程是必不可少的,我们来对比下传统直接包含 dylib
的时候大小,我们以 CoreImage.framework
来举例:
- 首先看下模拟器上的传统架构大小:
- 再看下对应的真机上的伪
framework
(包含tbd
)的大小
差距很明显了吧,对于真机来说,由于动态库都是在设备上,在 Xcode
上使用基于 tbd
格式的伪 framework
可以大大减少 Xcode
的大小。
题外话:网上有人说模拟器上还是使用 dylib
,的确没错。但是模拟器现在也桥了一层 tbd
格式,真正的 dylib
是在这个路径下: iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents
此外,虽然从 Xcode 7
时代到现在,一直都是 tbd
的说法,但是它也是经历了一些演变了,目前已经发展了 v3
格式的版本。
为什么拷贝tbd文件能解决Xcode 10上的问题
网上很多人都研究过 dyld
的代码,与之对应还有一种 ld
,就是平时我们在构建程序过程中,链接过程中出错的根因:
既然我们通过拷贝 tbd
的方式能解决链接不过的问题,那我们就要知道 ld
是如何运用 tbd
文件的。
既然报错事 library not found
,我们扒一下 linker
的源码即可:
Options::FileInfo Options::findLibrary(const char* rootName, bool dylibsOnly) const { FileInfo result; const int rootNameLen = strlen(rootName); // if rootName ends in .o there is no .a vs .dylib choice if ( (rootNameLen > 3) && (strcmp(&rootName[rootNameLen-2], ".o") == 0) ) { for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin(); it != fLibrarySearchPaths.end(); it++) { const char* dir = *it; if ( checkForFile("%s/%s", dir, rootName, result) ) return result; } } else { bool lookForDylibs = false; switch ( fOutputKind ) { case Options::kDynamicExecutable: case Options::kDynamicLibrary: case Options::kDynamicBundle: case Options::kObjectFile: // <rdar://problem/15914513> lookForDylibs = true; break; case Options::kStaticExecutable: case Options::kDyld: case Options::kPreload: case Options::kKextBundle: lookForDylibs = false; break; } switch ( fLibrarySearchMode ) { case kSearchAllDirsForDylibsThenAllDirsForArchives: // first look in all directories for just for dylibs if ( lookForDylibs ) { for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin(); it != fLibrarySearchPaths.end(); it++) { const char* dir = *it; auto path = std::string(dir) + "/lib" + rootName + ".dylib"; if ( findFile(path, {".tbd"}, result) ) return result; } for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin(); it != fLibrarySearchPaths.end(); it++) { const char* dir = *it; if ( checkForFile("%s/lib%s.so", dir, rootName, result) ) return result; } } // next look in all directories for just for archives if ( !dylibsOnly ) { for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin(); it != fLibrarySearchPaths.end(); it++) { const char* dir = *it; if ( checkForFile("%s/lib%s.a", dir, rootName, result) ) return result; } } break; case kSearchDylibAndArchiveInEachDir: // look in each directory for just for a dylib then for an archive for (std::vector<const char*>::const_iterator it = fLibrarySearchPaths.begin(); it != fLibrarySearchPaths.end(); it++) { const char* dir = *it; auto path = std::string(dir) + "/lib" + rootName + ".dylib"; if ( lookForDylibs && findFile(path, {".tbd"}, result) ) return result; if ( lookForDylibs && checkForFile("%s/lib%s.so", dir, rootName, result) ) return result; if ( !dylibsOnly && checkForFile("%s/lib%s.a", dir, rootName, result) ) return result; } break; } } throwf("library not found for -l%s", rootName); }
-
throwf("library not found for -l%s", rootName);
这里我们就找到了错误发生的原因,我们再往上溯源,找到linker
处理编译单元的入口: -
InputFiles::addOtherLinkerOptions
里面存在如下代码:CStringSet newLibraries = std::move(state.unprocessedLinkerOptionLibraries); state.unprocessedLinkerOptionLibraries.clear(); for (const char* libName : newLibraries) { if ( state.linkerOptionLibraries.count(libName) ) continue; try { Options::FileInfo info = _options.findLibrary(libName); if ( ! this->libraryAlreadyLoaded(info.path) ) { _linkerOptionOrdinal = _linkerOptionOrdinal.nextLinkerOptionOrdinal(); info.ordinal = _linkerOptionOrdinal; //<rdar://problem/17787306> -force_load_swift_libs info.options.fForceLoad = _options.forceLoadSwiftLibs() && (strncmp(libName, "swift", 5) == 0); ld::File* reader = this->makeFile(info, true); ld::dylib::File* dylibReader = dynamic_cast<ld::dylib::File*>(reader); ld::archive::File* archiveReader = dynamic_cast<ld::archive::File*>(reader); if ( dylibReader != NULL ) { dylibReader->forEachAtom(handler); dylibReader->setImplicitlyLinked(); dylibReader->setSpeculativelyLoaded(); this->addDylib(dylibReader, info); } else if ( archiveReader != NULL ) { _searchLibraries.push_back(LibraryInfo(archiveReader)); _options.addDependency(Options::depArchive, archiveReader->path()); //<rdar://problem/17787306> -force_load_swift_libs if (info.options.fForceLoad) { archiveReader->forEachAtom(handler); } } else { throwf("linker option dylib at %s is not a dylib", info.path); } } } catch (const char* msg) { // <rdar://problem/40829444> only warn about missing auto-linked library if some missing symbol error happens later state.missingLinkerOptionLibraries.insert(libName); } state.linkerOptionLibraries.insert(libName); }
而上述这些需要查询的 library
是从哪里来的呢?
-
我们以
xcconfig
举例来看:OTHER_LDFLAGS = $(inherited) -ObjC -l"stdc++"
在链接过程中就需要处理这样的 stdc++
Library ,而查询的方式就是在特定目录结构中搜索是否有对应的库文件或者 tbd
文件。
后记
使用 tbd
当然不止减少 Xcode
体积大小这一个好处,嘿嘿,你们自己摸索下吧~
而且,基于这种思路,能玩出许多类似文体两开花,中美合拍美猴王的玩法,加油吧。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。