如何在真正需要时才动态加载xcframework,而不是在应用启动时就加载?
我正在以二进制xcframework的形式分发一个专有的iOS SDK。该SDK包含一些需要其他外部依赖(也是xcframework)才能工作的功能。
这些外部依赖体积相当大,而且并非所有SDK客户端都会使用需要它们的功能。因此,我们决定分发一个不包含需要这些大型依赖项的功能的SDK版本。目前,这一行为由编译标志控制,例如:
#if EXTERNAL_DEPENDENCY
import SomeDependency
#endif
class SomeSDKComponent {
// ... other code
#if EXTERNAL_DEPENDENCY
func thisMethodUsesTheExternalDependencies() { ... }
#endif
// ... other code
}
如果SDK客户端想要使用需要那些外部依赖的功能,我们会在构建时把 EXTERNAL_DEPENDENCY 打开。
在Xcode中,这些依赖被设置为“Do Not Embed”,以保持xcframework的体积较小;如果客户端需要,我们会单独分发这些依赖。

尽管这方法可行,我更想要能够移除编译标志。
也就是说,我想能够在不包含所有 #if 的情况下编译 SDK:
import SomeDependency
class SomeSDKComponent {
// ... other code
func thisMethodUsesTheExternalDependencies() { ... }
// ... other code
}
并且我期望有以下三点:
- 如果客户端从未使用
thisMethodUsesTheExternalDependencies,在是否包含外部依赖的情况下,他们的应用都应能正常工作。 - 如果客户端调用
thisMethodUsesTheExternalDependencies,并且包含外部依赖,则他们的应用应按预期工作。 - 如果客户端调用
thisMethodUsesTheExternalDependencies,但未包含外部依赖,则在调用时他们的应用应崩溃。
我该如何实现?
如果我只是简单地移除 #if,那么如果客户端不包含外部依赖,应用在启动后立即就会崩溃。错误信息显示
dyld[48209]: Library not loaded: @rpath/ExternalDependency.framework/ExternalDependency
Referenced from: <9F14FB92-BA4C-31E7-A4C9-7C04941377AA> /private/var/containers/Bundle/Application/D81D65F6-81C5-4696-B1BD-854B39473EB2/ClientApp.app/Frameworks/MyProprietarySDK.framework/MyProprietarySDK
...随后是一系列它尝试搜索的路径。
因此,看来应用在启动时总是在尝试加载这些库。
由于外部依赖是动态链接的,我的理解是链接理论上可以在首次调用 thisMethodUsesTheExternalDependencies 时发生,而不是在应用启动时发生。是否有相关的构建设置?
解决方案
尽管这是一个带下划线的属性,但我发现通过使用 @_weakLinked 导入依赖可以达到预期的效果
@_weakLinked import SomeDependency
在 [underscored attributes reference] 的 @_weakLinked 描述中,恰好描述了我想要的行为(以下强调为我所加):
允许对一个声明进行弱引用,即客户端模块对该声明符号的任何引用都将具有弱链接性。这意味着客户端代码在运行时无法保证该符号可用时也能编译。这需要一个动态安全检查(例如使用dlsym(3));否则,在符号不可用时访问它会导致运行时崩溃。
这也给了我一个想法:在 thisMethodUsesTheExternalDependencies 中进行运行时检查,如果未将框架链接,则抛出错误,这比崩溃应用要优雅。
由于 SomeDependency 恰好是一个Objective-C框架,我可以使用 NSClassFromString 来检查框架中的某个类是否已加载。