2

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!

Travis Griggs
  • 21,522
  • 19
  • 91
  • 167
  • How do you plan on calling an extension like this? Can you add what the calling code would look like? – ryantxr Mar 22 '16 at 16:13

2 Answers2

3

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
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • OK, this works, but I have more questions than before! I thought one couldn't extend templated structs, but had to extend protocols with where refinements? But clearly, I was wrong about that? But then what confuses me is that you use `IntegerType` rather than `UInt8`? I tried UInt8, and it got mad because UInt8 was a type rather than a protocol. – Travis Griggs Mar 22 '16 at 16:26
  • Also, I didn't need to use the withUnsafeBufferPointer to make the compiler happy. What undesirable behavior would occur if I just skipped that wrapper and used the default NSData(bytes: self...)? – Travis Griggs Mar 22 '16 at 16:27
  • 1
    @TravisGriggs: As of Xcode 7 beta 2 you can extend restricted array types. But you can restrict the element type only to a protocol, not to a concrete type, that's why I chose IntegerType. – Lookup the online help of withUnsafeBufferPointer. It makes sure that the array elements are in contiguous storage, that is otherwise not guaranteed. – Martin R Mar 22 '16 at 16:29
  • Does that mean I can extend other templated structs as well, as long as I stick to protocol types in the refinements? Or is it special for Array? – Travis Griggs Mar 22 '16 at 16:31
  • @TravisGriggs: Yes, you can. – It is not special for Array. – Martin R Mar 22 '16 at 16:32
  • 1
    Ah yes, found this rather terse comment in the 7.0 release notes: "Public extensions of generic types are now permitted." And nothing else. I wonder why they limit it to Protocol types in the where clauses. – Travis Griggs Mar 22 '16 at 16:38
1

This seems to do what you want.

protocol ByteOnly: IntegerType {}
extension UInt8: ByteOnly {}

extension Array where Element: ByteOnly {
    var n : NSData { return NSData(bytes: self, length: self.count) }
}
// does it work with UInt8
var input5:[UInt8] = [0x61, 0x0D, 0xF1, 0x0D]
let data5 = input5.n // YES

// does it work on ints?
var ints: [Int] = [3,4,5,6,7,8]
let idata5 = ints.n // no
ryantxr
  • 4,119
  • 1
  • 11
  • 25