我在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。
答案 0 :(得分:2)
这是一个可能的解决方案:
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字符串。
答案 1 :(得分:0)
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。还有其他可能的方法,这些只是示例。
另请注意,上述内容假定您以只读方式访问RequestConfiguration。如果您还想修改它并使更改对第三方库C代码可见,那么它就是一个不同的球赛。