我正在我的iOS应用程序中使用第三方C库,我正在从Objective-C转换为Swift。在尝试读取 Swift 中的 C 库返回的结构之一时,我遇到了障碍。
结构如下所示:
typedef unsigned int LibUint;
typedef unsigned char LibUint8;
typedef struct RequestConfiguration_ {
LibUint8 names[30][128];
LibUint numberNames;
LibUint currentName;
} RequestConfiguration;
它作为元组导入到 Swift 中,其中包含 30 个元组的 128 个 LibUint8 值。在使用嵌套withUnsafePointer
调用进行了长时间的反复试验后,我最终开始寻找在 Swift 中迭代元组的解决方案。
我最终使用的是以下函数:
/**
* Perform iterator on every children of the type using reflection
*/
func iterateChildren<T>(reflectable: T, @noescape iterator: (String?, Any) -> Void) {
let mirror = Mirror(reflecting: reflectable)
for i in mirror.children {
iterator(i.label, i.value)
}
}
/**
* Returns a String containing the characters within the Tuple
*/
func libUint8TupleToString<T>(tuple: T) -> String {
var result = [CChar]()
let mirror = Mirror(reflecting: tuple)
for child in mirror.children {
let char = CChar(child.value as! LibUint8)
result.append(char)
// Null reached, skip the rest.
if char == 0 {
break;
}
}
// Always null terminate; faster than checking if last is null.
result.append(CChar(0))
return String.fromCString(result) ?? ""
}
/**
* Returns an array of Strings by decoding characters within the Tuple
*/
func libUint8StringsInTuple<T>(tuple: T, length: Int = 0) -> [String] {
var idx = 0
var strings = [String]()
iterateChildren(tuple) { (label, value) in
guard length > 0 && idx < length else { return }
let str = libUint8TupleToString(value)
strings.append(str)
idx++
}
return strings
}
用法
func handleConfiguration(config: RequestConfiguration) {
// Declaration types are added for clarity
let names: [String] = libUint8StringsInTuple(config.names, config.numberNames)
let currentName: String = names[config.currentName]
}
我的解决方案使用反射来迭代第一个元组,并使用反射来迭代第二个元组,因为在对嵌套元组使用 withUnsafePointer
时,我得到的字符串不正确,我认为这是由于标牌造成的。当然,必须有一种方法可以使用类似UnsafePointer
withUsafePointer(&struct.cstring) { String.fromCString(UnsafePointer($0)) }
读取数组中的 C 字符串。
需要明确的是,我正在寻找在 Swift 中读取这些 C 字符串的最快方法,即使这涉及使用 Reflection。
这是一个可能的解决方案:
func handleConfiguration(var config: RequestConfiguration) {
let numStrings = Int(config.numberNames)
let lenStrings = sizeofValue(config.names.0)
let names = (0 ..< numStrings).map { idx in
withUnsafePointer(&config.names) {
String.fromCString(UnsafePointer<CChar>($0) + idx * lenStrings) ?? ""
}
}
let currentName = names[Int(config.currentName)]
print(names, currentName)
}
它利用了以下事实:
LibUint8 names[30][128];
是内存中的 30*128 个连续字节。 withUnsafePointer(&config.names)
使用 $0
调用闭包作为指向其开头的指针内存位置,以及
UnsafePointer<CChar>($0) + idx * lenStrings
是指向 IDX-th 子数组开头的指针。上面的代码需要每个子数组都包含一个以 NUL 结尾的 UTF-8 字符串。
Martin R 建议的解决方案对我来说看起来不错,据我有限的测试来看,确实有效。 但是,正如 Martin 指出的那样,它要求字符串以 NUL 结尾的 UTF-8。 以下是另外两种可能的方法。 这些遵循的原则是在 C 中处理 C 数据结构的复杂性,而不是在 Swift 中处理它。选择哪种方法取决于您在应用中具体对 RequestConfiguration 执行的操作。 如果你不习惯用 C 语言编程,那么纯粹的 Swift 方法,比如 Martin 建议的方法,可能是更好的选择。
出于此讨论的目的,我们将假设第三方 C 库具有以下用于检索 RequestConfiguration的函数:
const RequestConfiguration * getConfig();
方法 1:使 RequestConfiguration 对象可用于您的 Swift 代码,但使用以下 C 帮助程序函数从中提取名称:
const unsigned char * getNameFromConfig(const RequestConfiguration * rc, unsigned int nameIdx)
{
return rc->names[nameIdx];
}
此函数的签名和 RequestConfiguration 类型都必须通过桥接标头提供给 Swift 代码。 然后你可以在 Swift 中做这样的事情:
var cfg : UnsafePointer<RequestConfiguration> = getConfig()
if let s = String.fromCString(UnsafePointer<CChar>(getNameFromConfig(cfg, cfg.memory.currentName)))
{
print(s)
}
例如,如果您需要 Swift 可用的 RequestConfiguration 对象来检查多个位置的名称数量,则此方法非常有用。
方法2:您只需要能够在给定位置获得名称。 在这种情况下,RequestConfiguration 类型甚至不需要对 Swift 可见。 您可以像这样编写一个帮助程序 C 函数:
const unsigned char * getNameFromConfig1(unsigned int idx)
{
const RequestConfiguration * p = getConfig();
return p->names[idx];
}
并在 Swift 中使用它,如下所示:
if let s = String.fromCString(UnsafePointer<CChar>(getNameFromConfig1(2)))
{
print(s)
}
这将在位置 2 打印名称(从 0 开始计数(。 当然,使用此方法时,您可能还需要返回名称计数以及当前名称索引的 C 帮助程序。
同样,使用这两种方法,假设字符串是 NUL 终止的 UTF-8。 还有其他可能的方法,这些只是示例。
另请注意,上述假设您以只读方式访问请求配置。 如果您还想修改它并使更改对第三方库 C 代码可见,那么这是一个不同的球赛。