在Swift中运行时检查第三方框架的弱链接符号

5

在 macOS 上,我使用了一个外部的 C 语言编写的框架,必须由用户安装。在 Swift 中,我需要在运行时检查它是否存在,但我不能使用 #available(),因为它只是用于检测操作系统相关的特性,而我需要追踪一个外部框架。另外,NSClassFromString() 对于非 Objective-C 框架也没有用。

我一直在尝试理解如何复制 Objective-C 的等效方法来检查弱链接符号,例如:

if ( anExternalFunction == NULL ) {
    // fail graciously
} else {
    // do my thing
}

但在Swift中,这似乎行不通:编译器指出由于anExternalFunction不是可选的,我将始终得到!= nil,这听起来很“Swift”,但对我没有帮助。

我找到了两种解决方案,但它们会让我的代码变得难以忍受:

选项1,创建一个名为isFrameworkAvailable()的Objective-C文件,执行工作,并从Swift调用。

选项2,使用以下Swift代码实际检查库:

let libHandle = dlopen("/Library/Frameworks/TheLib.framework/TheLib", RTLD_NOW)

if (libHandle != nil) {
    if dlsym(libHandle, "anExternalFunction") != nil {
        return true
    }
}
return false

因为某种原因,我无法将选项2与RTLD_DEFAULT很好地配合使用,因为它在dlfcn.h中被定义(-2),但似乎没有被Swift导入(就像该头文件中的所有负指针一样:RTLD_NEXT、RTLD_DEFAULT、RTLD_SELF、RTLD_MAIN_ONLY)。我发现了这个最丑陋的hack来使它正常工作:

if dlsym(unsafeBitCast(-2, to: UnsafeMutableRawPointer.self), "anExternalFunction") != nil {
    // library installed
}

对于Option 2,我需要路径或一种hack方法,虽然这种方法特别丑陋(但有效),而Option 1不太“Swifty”,并且对我来说没有什么意义:在Swift中正确的做法是什么?

这个行得通吗:https://dev59.com/VmAf5IYBdhLWcg3weyzZ - rmaddy
不,它不支持,因为我使用的第三方框架不是Objective-C框架,而是一个纯C库。(已更新问题) - Daniel
这看起来很相关(重复?)在Swift中检查全局函数的存在性 - Martin R
它很有用,但我不会称其为重复:答案来自2016年(这意味着不是Swift 4),并且在稍微不同的上下文中提出了问题,在询问之前我进行了长达一小时的调查,也没有找到它。此外,使用RTLD_SELF的解决方案无法编译(未定义)。可能存在Swift 4的新解决方案。 - Daniel
1
类似于 #define RTLD_DEFAULT ((void *) -2 这样的 C 宏并没有被导入到 Swift 中,但是 if dlsym(UnsafeMutableRawPointer(bitPattern: -2), "anExternalFunction") ... 会更加简洁。- 参考 https://forums.developer.apple.com/thread/18816。 - Martin R
授权,我会更改,但它仍然是硬编码的。当我想要真正的花生酱时,这一切都感觉像豆腐黄油:以上内容都不是非常Swifty。 - Daniel
1个回答

1
我找到了两种做法:
    1.
if let _ = dlopen("/Library/Frameworks/MyLibrary.framework/MyLibrary", RTLD_NOW) {
  print("Can open my library")
}

2. (在有序列表中排第二位)
#if canImport(MyLibrary)
  print("Can import my library")
#endif

第二种方法从易读性和类型安全性方面显然更好,但我不确定两种方法之间的权衡是什么。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接