来自导入的无符号字符 2D 数组的快速字符串



我正在我的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 代码可见,那么这是一个不同的球赛。

最新更新