37

I'm trying to store an array of integers to disk in swift. I can get them into an NSData object to store, but getting them back out into an array is difficult. I can get a raw COpaquePointer to the data with data.bytes but can't find a way to initialize a new swift array with that pointer. Does anyone know how to do it?

import Foundation

var arr : UInt32[] = [32,4,123,4,5,2];

let data = NSData(bytes: arr, length: arr.count * sizeof(UInt32))

println(data)  //data looks good in the inspector

// now get it back into an array?
tassinari
  • 2,313
  • 2
  • 24
  • 32
  • 5
    archive / unarchive it, that is much easier and most common way to do such a thing. – holex Jul 01 '14 at 17:37
  • 1
    archive/unarchive crashes here because it's not an array of objects, just int values. I would have to change my array to Int objects. – tassinari Jul 01 '14 at 18:09
  • sure, because you can archive objects only which conforms to `NSCoding` protocol. if you change the array from `Array` to `Array`, you will be able to archive immediately. – holex Jul 01 '14 at 18:20

4 Answers4

79

You can use the getBytes method of NSData:

// the number of elements:
let count = data.length / sizeof(UInt32)

// create array of appropriate length:
var array = [UInt32](count: count, repeatedValue: 0)

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

print(array)
// Output: [32, 4, 123, 4, 5, 2]

Update for Swift 3 (Xcode 8): Swift 3 has a new type struct Data which is a wrapper for NS(Mutable)Data with proper value semantics. The accessor methods are slightly different.

Array to Data:

var arr: [UInt32] = [32, 4, UInt32.max]
let data = Data(buffer: UnsafeBufferPointer(start: &arr, count: arr.count))
print(data) // <20000000 04000000 ffffffff>

Data to Array:

let arr2 = data.withUnsafeBytes {
    Array(UnsafeBufferPointer<UInt32>(start: $0, count: data.count/MemoryLayout<UInt32>.stride))
}
print(arr2) // [32, 4, 4294967295]

Update for Swift 5:

Array to Data:

let arr: [UInt32] = [32, 4, UInt32.max]
let data = Data(buffer: UnsafeBufferPointer(start: arr, count: arr.count))
print(data) // <20000000 04000000 ffffffff>

Data to Array:

var arr2 = Array<UInt32>(repeating: 0, count: data.count/MemoryLayout<UInt32>.stride)
_ = arr2.withUnsafeMutableBytes { data.copyBytes(to: $0) }
print(arr2) // [32, 4, 4294967295]
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    Swift changed the way to write array declarations, in Xcode Beta 5 you get an error saying: `Array types are now written with the brackets around the element type`. So in the 2nd line of your code the correct is: `var array = [UInt32](count: count, repeatedValue: 6)` – Lucien Aug 08 '14 at 15:41
  • @Martin R: may i ask another similar question? // CGFloat to NSData var cgf: CGFloat = 5.123456 var f = Float(cgf) var nsf = NSNumber(float: f) var nsd = NSData(bytes: &nsf, length: sizeofValue(nsf)) The question is how can i convert nsdata back to CGFloat? Thanks in advance. – Sifeng Sep 07 '14 at 20:36
  • @Sifeng: `nsf` is a *pointer* to an object, so you cannot use that to pack the number into data. Just `var cgf: CGFloat = 5.123456 ; var data = NSData(bytes: &cgf, length: sizeofValue(cgf))` to pack and `var cgf1: CGFloat = 0 ; data.getBytes(&cgf1, length: sizeofValue(cgf1))` to unpack. – Martin R Sep 07 '14 at 20:43
  • Wouldn't data length be divided by strideof(UInt32) since strideof measures the width of array elements e.g. type T in Array ? – Bobjt Mar 18 '16 at 17:14
  • 1
    @Bobjt: It would not make a difference for "simple" types like Int, but generally you are right! – Martin R Mar 18 '16 at 18:46
  • I think this answer is correct for `UInt32` but incorrect in the general case. The call to `UnsafeBufferPointer` should calculate the count by doing `data.count / MemoryLayout.stride`. If the stride is greater than the size, then using size will calculate an incorrect count. – algal Jan 26 '18 at 19:38
  • @algal: `size` and `stride` are identical for all "simple" integer and floating point types (and for all types imported from C). But you are right, `stride` is the correct thing in the general case. Thanks for the feedback! – Martin R Jan 26 '18 at 19:42
  • @MartinR My pleasure. I believe you may have explained this matter to me originally, on an SO question not so long ago. :) – algal Jan 26 '18 at 23:59
  • withUnsafeBufferPointer is now deprecated – iSpain17 Aug 16 '19 at 09:04
15

It's also possible to do this using an UnsafeBufferPointer, which is essentially an "array pointer", as it implements the Sequence protocol:

let data = NSData(/* ... */)

// Have to cast the pointer to the right size
let pointer = UnsafePointer<UInt32>(data.bytes)
let count = data.length / 4

// Get our buffer pointer and make an array out of it
let buffer = UnsafeBufferPointer<UInt32>(start:pointer, count:count)
let array = [UInt32](buffer)

This eliminates the need for initializing an empty array with duplicated elements first, to then overwrite it, although I have no idea if it's any faster. As it uses the Sequence protocol this implies iteration rather than fast memory copy, though I don't know if it's optimized when passed a buffer pointer. Then again, I'm not sure how fast the "create an empty array with X identical elements" initializer is either.

Daniel Bruce
  • 11,269
  • 4
  • 30
  • 28
  • This seems to currently work better, as the getBytes method was causing a crash. – voidref Apr 29 '15 at 22:38
  • 6
    seems getBytes is much faster, compared to this one. For 1349890 bytes using an "array pointer" took 0.034 sec, while getBytes only 0.005 sec – f3n1kc Jul 27 '15 at 16:55
  • Getting following error : 'UnsafePointer' with an argument list of type '(UnsafeRawPointer)'. because data.bytes is UnsafeRawPointer. I am just trying to write equivalent code for const char* bytes = [data bytes]; – Alok C Dec 02 '16 at 00:07
  • Running into the same issue as Alix. – Emmo213 Jul 14 '17 at 15:48
1

Here is a generic way to do it.

import Foundation

extension Data {
    func elements <T> () -> [T] {
        return withUnsafeBytes {
            Array(UnsafeBufferPointer<T>(start: $0, count: count/MemoryLayout<T>.size))
        }
    }
}

let array = [1, 2, 3]
let data = Data(buffer: UnsafeBufferPointer(start: array, count: array.count))
let array2: [Int] = data.elements()

array == array2
// IN THE PLAYGROUND, THIS SHOWS AS TRUE

You must specify the type in the array2 line. Otherwise, the compiler cannot guess.

William Entriken
  • 37,208
  • 23
  • 149
  • 195
1

If you are dealing with Data to Array (I know for sure my array is going to be [String]), I am quite happy with this:

NSKeyedUnarchiver.unarchiveObject(with: yourData)

I hope it helps

Michel Goñi
  • 107
  • 1
  • 8