Search code examples
swiftswift3libxml2unsafe-pointers

A swiftier way to convert String to UnsafePointer<xmlChar> in Swift 3 (libxml2)


I'm working on a Swift 3 wrapper for the libxml2 C-library.

There are two convenience methods to convert String to UnsafePointer<xmlChar> and vice versa. In libxml2 xmlChar is declared as unsigned char.

  • UnsafePointer<xmlChar> to String is uncomplicated

    func stringFrom(xmlchar: UnsafePointer<xmlChar>) -> String {
        let string = xmlchar.withMemoryRebound(to: CChar.self, capacity: 1) {
            return String(validatingUTF8: $0)
        }
        return string ?? ""
    }
    
  • For String to UnsafePointer<xmlChar> I tried many things for example

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

    but this doesn't work, the only working solution I figured out is

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

Is there a better, swiftier way without the bridge cast to NSString and unsafeBitCast?


Solution

  • Swiftiest way I can think of is to just use the bitPattern: initializer:

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

    This will give you an Array of xmlChars. Hang onto that, and use Array's withUnsafeBufferPointer method when you need to pass an UnsafePointer to something:

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

    Don't let the UnsafePointer escape from the closure, as it won't be valid outside it.

    EDIT: How's this for a compromise? Instead of having your function return a pointer, have it take a closure.

    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!) }
    }
    

    Or, as an extension on 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!) }
        }
    }