【问题标题】:A swiftier way to convert String to UnsafePointer<xmlChar> in Swift 3 (libxml2)在 Swift 3 (libxml2) 中将 String 转换为 UnsafePointer<xmlChar> 的更快捷方式
【发布时间】:2018-02-08 11:16:15
【问题描述】:

我正在为 libxml2 C 库开发 Swift 3 包装器。

有两种方便的方法可以将String 转换为UnsafePointer&lt;xmlChar&gt;,反之亦然。在 libxml2 中,xmlChar 被声明为 unsigned char

  • UnsafePointer&lt;xmlChar&gt;String 并不复杂

    func stringFrom(xmlchar: UnsafePointer<xmlChar>) -> String {
        let string = xmlchar.withMemoryRebound(to: CChar.self, capacity: 1) {
            return String(validatingUTF8: $0)
        }
        return string ?? ""
    }
    
  • 例如,对于StringUnsafePointer&lt;xmlChar&gt;,我尝试了很多方法

    let bytes = string.utf8CString.map{ xmlChar($0) }
    return UnsafePointer<xmlChar>(bytes)
    

    但这不起作用,我想出的唯一可行的解​​决方案是

    func xmlCharFrom(string: String) -> UnsafePointer<xmlChar> {
        let pointer = (string as NSString).utf8String
        return unsafeBitCast(pointer, to: UnsafePointer<xmlChar>.self)
    }
    

有没有更好的,swiftier 的方法,无需将桥转换为 NSStringunsafeBitCast

【问题讨论】:

  • 您的 stringFrom(xmlchar:) 方法未正确实现,capacity: 应该是您接收的缓冲区的长度,不一定是 1。我会改为使用 String.decodeCString(xmlchar, as: UTF8.self, repairingInvalidCodeUnits: false)?.result ?? ""。您对xmlCharFrom 的实现都不正确;你试图逃避你不能控制生命周期的指针,在这两种情况下你都会得到未定义的行为。

标签: swift swift3 libxml2 unsafe-pointers


【解决方案1】:

我能想到的最快捷的方法就是使用 bitPattern: 初始化器:

let xmlstr = str.utf8CString.map { xmlChar(bitPattern: $0) }

这将为您提供ArrayxmlChars。坚持下去,当您需要将UnsafePointer 传递给某物时,请使用ArraywithUnsafeBufferPointer 方法:

xmlstr.withUnsafeBufferPointer { someAPIThatWantsAPointer($0.baseAddress!) }

不要让 UnsafePointer 从闭包中逃逸,因为它在闭包之外无效。

编辑:如何妥协?不要让你的函数返回一个指针,而是让它接受一个闭包。

func withXmlString<T>(from string: String, handler: (UnsafePointer<xmlChar>) throws -> T) rethrows -> T {
    let xmlstr = string.utf8CString.map { xmlChar(bitPattern: $0) }

    return try xmlstr.withUnsafeBufferPointer { try handler($0.baseAddress!) }
}

或者,作为String的扩展:

extension String {
    func withXmlString<T>(handler: (UnsafePointer<xmlChar>) throws -> T) rethrows -> T {
        let xmlstr = self.utf8CString.map { xmlChar(bitPattern: $0) }

        return try xmlstr.withUnsafeBufferPointer { try handler($0.baseAddress!) }
    }
}

【讨论】:

  • 我也想过,但是我需要从方法中返回指针。
  • @vadian 你需要手动管理内存——你知道什么时候需要释放指针吗?
  • 您应该重新考虑是否真的需要这样做。基本上没有任何方法可以被认为是任何形状或形式的“Swifty”。话虽如此,如果你真的,真的必须返回一个指针,我能想到的唯一安全的方法是分配一个新的UnsafeMutablePointer,将字节复制到它,然后返回它。然后,您将负责最终释放指针所指的内存。
  • 编辑了可能妥协的答案。
  • 在 Swift 4 中你也可以说:func withXmlString(from string: String, handler: (UnsafePointer&lt;xmlChar&gt;) -&gt; ()) { string.withCString(encodedAs: UTF8.self, handler) }.
【解决方案2】:

我正在为 libxml2 C 库开发 Swift 3 包装器。

慰问。

[...] 到 UnsafePointer 的字符串 [很复杂]

同意。这很复杂,因为不清楚谁拥有 xmlChar 数组。

[...] 我想出的唯一可行的解​​决方案是

let pointer = (string as NSString).utf8String

这是因为-[NSString utf8String] 的所有权语义:

苹果文档:

这个C字符串是一个指向字符串对象内部结构的指针,它的生命周期可能比字符串对象短,而且肯定不会有更长的生命周期。

所以生命周期可能类似于当前的自动释放池,甚至更短,这取决于编译器的 ARC 优化和 utf8String 的实现。绝对不安全。

有没有更好、更快捷的方法 [...]?

嗯,这取决于用例。如果不考虑创建的 xmlChar 缓冲区的所有权,就无法处理这个问题。

从 API 中应该清楚函数是如何使用传递的字符串的(尽管我知道 libxml2 的文档很糟糕)。

对于在函数调用期间仅使用字符串的情况,使用范围访问函数可能会很好:

extension String {
    func withXmlChar(block: (UnsafePointer<xmlChar>) -> ()) { ... }
}

如果函数保留指针,则必须保证指针的生命周期。可能类似于一个容器对象,它保留了一个 Data 和指针,以维持一些 ARC 维护的生命周期......

了解one of Mike Ash's recent articles 可能是值得的,它是关于管理 ARC 之外的对象的所有权。

【讨论】:

  • 多谢慰问,没那么难过。我得到了大部分实现(包括 XPath 支持)。实际上这是一个 ObjC 项目,我要移植到 Swift。
【解决方案3】:

String 有一个

public init(cString: UnsafePointer<UInt8>)

初始化器,因此从 XML 字符串到 Swift 字符串的转换可以简化为

let xmlString: UnsafePointer<xmlChar> = ...
let s = String(cString: xmlString)

格式错误的 UTF-8 序列被 Unicode 替换 字符U+FFFD


对于从 Swift 字符串到 XML 字符串的转换,我建议 similar approach as Charles Srstka,但使用 现有的String.withCString 方法而不是创建中间 数组:

extension String {
    func withXmlString<T>(handler: (UnsafePointer<xmlChar>) throws -> T) rethrows -> T {
        return try self.withCString { try handler(UnsafeRawPointer($0).assumingMemoryBound(to: UInt8.self)) }
    }
}

如果不需要投掷选项,则简化为

extension String {
    func withXmlString<T>(handler: (UnsafePointer<xmlChar>) -> T) -> T {
        return self.withCString { handler(UnsafeRawPointer($0).assumingMemoryBound(to: UInt8.self)) }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多