Create an Array in Swift from an NSData Object
Solution 1
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]
Solution 2
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.
Solution 3
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
Solution 4
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.
tassinari
Updated on July 17, 2022Comments
-
tassinari almost 2 years
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 withdata.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?
-
Lucien almost 10 yearsSwift 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)
-
Sifeng over 9 years@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.
-
Martin R over 9 years@Sifeng:
nsf
is a pointer to an object, so you cannot use that to pack the number into data. Justvar cgf: CGFloat = 5.123456 ; var data = NSData(bytes: &cgf, length: sizeofValue(cgf))
to pack andvar cgf1: CGFloat = 0 ; data.getBytes(&cgf1, length: sizeofValue(cgf1))
to unpack. -
voidref about 9 yearsThis seems to currently work better, as the getBytes method was causing a crash.
-
f3n1kc almost 9 yearsseems 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
-
Bobjt about 8 yearsWouldn't data length be divided by strideof(UInt32) since strideof measures the width of array elements e.g. type T in Array<T> ?
-
Martin R about 8 years@Bobjt: It would not make a difference for "simple" types like Int, but generally you are right!
-
Alix over 7 yearsGetting following error : 'UnsafePointer<UInt8>' 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];
-
Emmo213 almost 7 yearsRunning into the same issue as Alix.
-
algal over 6 yearsI think this answer is correct for
UInt32
but incorrect in the general case. The call toUnsafeBufferPointer
should calculate the count by doingdata.count / MemoryLayout<Type>.stride
. If the stride is greater than the size, then using size will calculate an incorrect count. -
Martin R over 6 years@algal:
size
andstride
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! -
algal over 6 years@MartinR My pleasure. I believe you may have explained this matter to me originally, on an SO question not so long ago. :)
-
iSpain17 over 4 yearswithUnsafeBufferPointer is now deprecated