Why does Swift return an unexpected pointer when converting an optional String into an UnsafePointer?

后端 未结 3 891
无人共我
无人共我 2021-01-06 02:12

I noticed some unusual behaviour when working with a C library which took strings in as const char * (which is converted to Swift as UnsafePointer

相关标签:
3条回答
  • 2021-01-06 02:42

    As mentioned in the comments, this is a clear bug in Swift.

    Here's a workaround I'm using. If you can't trust Swift to convert the strings to pointers for you, then you've got to do it yourself.

    Assuming C function defined as:

    void multiString(const char *arg0, const char *arg1, const char *arg2);
    

    Swift code:

    func callCFunction(arg0: String?, arg1: String?, arg2: String?) {
        let dArg0 = arg0?.data(using: .utf8) as NSData?
        let pArg0 = dArg0?.bytes.assumingMemoryBound(to: Int8.self)
    
        let dArg1 = arg1?.data(using: .utf8) as NSData?
        let pArg1 = dArg1?.bytes.assumingMemoryBound(to: Int8.self)
    
        let dArg2 = arg2?.data(using: .utf8) as NSData?
        let pArg2 = dArg2?.bytes.assumingMemoryBound(to: Int8.self)
    
        multiString(pArg1, pArg2, pArg3)
    }
    

    Warning:

    Don't be tempted to put this in a function like:

    /* DO NOT USE -- BAD CODE */
    func ocstr(_ str: String?) -> UnsafePointer<Int8>? {
        guard let str = str else {
            return nil
        }
    
        let nsd = str.data(using: .utf8)! as NSData
    
        //This pointer is invalid on return:
        return nsd.bytes.assumingMemoryBound(to: Int8.self)
    }
    

    which would remove repeated code. This doesn't work because the data object nsd gets deallocated at the end of the function. The pointer is therefore not valid on return.

    0 讨论(0)
  • 2021-01-06 02:44

    Just to be clear, there is a workaround until Apple fixes this. Unwrap your optional Strings before passing them and everything will work fine.

    var anOptional: String?
    var anotherOptional: String?
    
    func mySwiftFunc() {
    
        let unwrappedA = anOptional!
        let unwrappedB = anotherOptional!
    
        myCStringFunc(unwrappedA, unwrappedB)
    
    }
    
    0 讨论(0)
  • 2021-01-06 02:46

    I didn't find anything useful on if this is desired behaviour or just a bug.

    The pragmatic solution would probably be to just have a proxy method like this, but you probably did something similar already.

    func proxy(_ str: String?, _ functionToProxy: (UnsafePointer<UInt8>?) -> ()) {
        if let str = str {
            functionToProxy(str)
        } else {
            functionToProxy(nil)
        }
    }
    
    proxy(input, test)
    

    Did you test if it was working in Swift 2? They changed something maybe related in Swift 3:

    https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md

    0 讨论(0)
提交回复
热议问题