Search code examples
swiftswift-extensions

Swift: cannot convert value of type 'Self' to expected argument type 'UnsafePointer<Void>'


I do a bunch of work with [Uint8] arrays. But I often have to recast these as NSData objects to interface with iOS frameworks such as CoreBluetooth. So I have lots of code that might look something like:

var input:[UInt8] = [0x60, 0x0D, 0xF0, 0x0D]
let data = NSData(bytes: input, length: input.count)

Being tired of having to insert this extra let data = ... line, I thought I would just extend those arrays with a computed property to do the work. Then I could just do things like:

aBluetoothPeriperal.write(myBytes.nsdata, ...)

So it's basically just extension sugar. I can't extend Array, but I can extend a protocol:

extension SequenceType where Generator.Element == UInt8 {
    var nsdata:NSData {
        return NSData(bytes: self, length: self.count)
    }
}

Which produces an error that looks like:

Playground execution failed: MyPlayground.playground:3:24: error: cannot convert value of type 'Self' to expected argument type 'UnsafePointer<Void>' (aka 'UnsafePointer<()>')
                return NSData(bytes: self, length: self.count)
                                 ^~~~

Sadly, the more I use Swift--and I really do like some things about Swift--the more I'm reminded of my negative experiences with trying to understand reams of unhelpful compiler output when I tried my hand at C++ with lots of generics and stuff years ago. So please Obi Wan, help me see the light here!


Solution

  • NSData(bytes:, length:) takes an UnsafePointer<Void> as first parameter, and you cannot pass an arbitrary SequenceType here.

    You can pass an Array which would be passed as the address of the first array element. It is however not guaranteed that Array elements are stored in contiguous memory.

    Therefore:

    • Define an Array extension instead of a Sequence extension.
    • Use the withUnsafeBufferPointer() method to get a pointer to contiguous array storage.

    Possible solution:

    extension Array where Element : IntegerType {
        var nsdata : NSData {
            return self.withUnsafeBufferPointer {
                NSData(bytes: $0.baseAddress, length: self.count * strideof(Element))
            }
        }
    }
    
    let input:[UInt8] = [0x60, 0x0D, 0xF0, 0x0D]
    print(input.nsdata) // <600df00d>
    

    Swift does currently not allow to restrict an array extension to a concrete type:

    extension Array where Element == UInt8 { }
    // error: same-type requirement makes generic parameter 'Element' non-generic
    

    therefore it is generalized to any elements conforming to IntegerType.

    For a sequence you could then do a conversion to an array first:

    let data = Array(myUInt8sequence).nsdata