89

I couldn't find a solution to this problem in Swift (all of them are Objective-C, and they deal with pointers which I don't think exist in Swift in the same form). Is there any way to convert a NSData object into an array of bytes in the form of [Uint8] in Swift?

Bond
  • 16,071
  • 6
  • 30
  • 53
Bowen Su
  • 1,219
  • 3
  • 13
  • 15

7 Answers7

127

You can avoid first initialising the array to placeholder values, if you go through pointers in a slightly convoluted manner, or via the new Array constructor introduced in Swift 3:

Swift 3

let data = "foo".data(using: .utf8)!

// new constructor:
let array = [UInt8](data)

// …or old style through pointers:
let array = data.withUnsafeBytes {
    [UInt8](UnsafeBufferPointer(start: $0, count: data.count))
}

Swift 2

Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(data.bytes), count: data.length))
Arkku
  • 41,011
  • 10
  • 62
  • 84
  • Though clean and convenient, this uses an NSArray and may cause errors down the road... – califrench May 24 '16 at 03:07
  • 2
    @califrench I'm sorry, I didn't follow. 1) There is no `NSArray` that I can spot, the `bytes` accessor does not return it. 2) What errors would it cause down the road? If you're referring to possible unavailability of `NS*` classes, recall that the question specifies `NSData`. – Arkku May 24 '16 at 21:35
  • 1
    I'm sorry @Arkku, I got confused, I was thinking for a minute that `Array()` was relying on `NSArray` but I don't think it is... The issues down the road would have been trying to access an element using a subscript since `NSArray`s don't know what kind of elements they contain whereas Swift `Array`s do. – califrench May 25 '16 at 22:19
  • This fails with a compiler error. `Cannot invoke initializer for type 'UnsafePointer' with an argument list of type '(UnsafePointer)'` – Dan Loewenherz Jun 24 '16 at 13:01
  • 1
    @Dan It works just fine for me both in Swift 2.2 and Swift 3. In Swift 3 the preceding line is `let data : NSData! = "foo".data(using: String.Encoding.utf8)` – Arkku Jun 26 '16 at 11:11
  • 1
    @Arkku it doesn't compile with latest Swift, I had to change it to: array = data.withUnsafeBytes { bytes in Array( UnsafeBufferPointer(start: bytes, count: data.count) ) } – Zmey Jun 30 '16 at 00:07
  • @Zmey: True, it stopped working at some point during the XCode 8 betas. I've updated the answer with that solution, thanks! – Arkku Aug 25 '16 at 15:43
  • Data conforms to MutableCollection in Swift 3 so you can initialize an Array with it to get an array of UInt8. try `Array(data)` – Leo Dabus Dec 13 '16 at 17:34
  • 1
    @LeoDabus That's basically a different way of saying `[UInt8](data)`, which is already in the answer since the latest edit. (And IMO the clearer syntax with explicit type.) – Arkku Dec 13 '16 at 20:21
  • how to get signed bytes here? – Naga Mallesh Maddali Feb 19 '18 at 12:55
74

Swift 5 Solution

Data to [bytes]

extension Data {
    var bytes: [UInt8] {
        return [UInt8](self)
    }
}

[bytes] to Data

extension Array where Element == UInt8 {
    var data: Data {
        return Data(self)
    }
}
dcow
  • 7,765
  • 3
  • 45
  • 65
Maor
  • 3,340
  • 3
  • 29
  • 38
49

It's funny but exist more simple solution. Works in Swift 3. Surely. I've used this today.

data: Data // as function parameter    
let byteArray = [UInt8](data)

That's all! :) NSData easily bridged to Data.

UPDATE: (due to Andrew Koster comment)

Swift 4.1, Xcode 9.3.1

Just has been rechecked - all works as expected.

if let nsData = NSData(base64Encoded: "VGVzdFN0cmluZw==", options: .ignoreUnknownCharacters) {
let bytes = [UInt8](nsData as Data)
print(bytes, String(bytes: bytes, encoding: .utf8))

Output: [84, 101, 115, 116, 83, 116, 114, 105, 110, 103] Optional("TestString")

sVd
  • 1,043
  • 12
  • 12
  • 1
    This should be the accepted solution, now that Swift 3 is out. – Chrissi Nov 14 '16 at 16:48
  • This most likely makes a copy of your data. While small, not a big deal. If large, can cause a lot of issues. – Luke Nov 14 '16 at 21:24
  • @AndrewKoster pls look at my update. or provide detailed explanation – sVd May 28 '18 at 10:26
  • Looks like it works now. I don't know if the compiler changed or if your answer changed, but it compiles. – Andrew Koster Jun 01 '18 at 22:59
  • 1
    @AndrewKoster Yes, you do know: check the edit history of the answer. The original answer has not changed, only the update has been added. (And in case of my answer, where you made the same comment: it has not been edited between 2016 and now.) And of course you should know whether you updated your compiler or not. However, given that the answer has been working in official releases for a couple of years now, I think it is more likely that something else was broken when you previously tried it. In any case, answers cannot predict the future, so I question downvoting for "doesn't work anymore". – Arkku Jun 06 '18 at 15:55
  • Maybe I screwed up pasting this into my code? Maybe I changed something afterward without realizing? I didn't keep a record of every detail of this failed test. I tried a different way, which I posted here, and it worked. I voted this back up, since everyone says that it works now/still. – Andrew Koster Jun 13 '18 at 01:01
22

You can use the getBytes function of NSData to get the byte array equivalent.

As you did not provide any source code, I will use a Swift String contents that has been converted to NSData.

var string = "Hello World"
let data : NSData! = string.dataUsingEncoding(NSUTF8StringEncoding)

let count = data.length / sizeof(UInt8)

// create an array of Uint8
var array = [UInt8](count: count, repeatedValue: 0)

// copy bytes into array
data.getBytes(&array, length:count * sizeof(UInt8))

println(array)

Swift 3/4

let count = data.length / MemoryLayout<UInt8>.size

// create an array of Uint8
var byteArray = [UInt8](repeating: 0, count: count)
// copy bytes into array
data.getBytes(&byteArray, length:count)
Christian Abella
  • 5,747
  • 2
  • 30
  • 42
7

Swift 3/4

let data = Data(bytes: [0x01, 0x02, 0x03])
let byteArray: [UInt8] = data.map { $0 }
Al Zonke
  • 532
  • 6
  • 8
1

swift 4 and image data to a byte array.

 func getArrayOfBytesFromImage(imageData:Data) ->[UInt8]{

    let count = imageData.count / MemoryLayout<UInt8>.size
    var byteArray = [UInt8](repeating: 0, count: count)
    imageData.copyBytes(to: &byteArray, count:count)
    return byteArray

}
Pathak Ayush
  • 726
  • 12
  • 24
0

You can try

extension Data {
func toByteArray() -> [UInt8]? {
    var byteData = [UInt8](repeating:0, count: self.count)
    self.copyBytes(to: &byteData, count: self.count)
    return byteData
  }
}
Rachit Agarwal
  • 356
  • 2
  • 9