8

I have this output from NSData: <00000100 84000c00 071490fe 4dfbd7e9>

So how could I byte reverse it in Swift and have this output: <00000001 0084000c 1407fe90 fb4de9d7>?

peterh
  • 11,875
  • 18
  • 85
  • 108
Marco Almeida
  • 1,285
  • 3
  • 17
  • 39

3 Answers3

14

This should work to swap each pair of adjacent bytes in the data. The idea is to interpret the bytes as an array of UInt16 integers and use the built-in byteSwapped property.

func swapUInt16Data(data : NSData) -> NSData {
    
    // Copy data into UInt16 array:
    let count = data.length / sizeof(UInt16)
    var array = [UInt16](count: count, repeatedValue: 0)
    data.getBytes(&array, length: count * sizeof(UInt16))
    
    // Swap each integer:
    for i in 0 ..< count {
        array[i] = array[i].byteSwapped // *** (see below)
    }

    // Create NSData from array:
    return NSData(bytes: &array, length: count * sizeof(UInt16))
}

If your actual intention is to convert data from an (external) big-endian representation to the host (native) byte order (which happens to be little-endian on all current iOS and OS X devices) then you should replace *** by

array[i] = UInt16(bigEndian: array[i])

Example:

var bytes : [UInt8] = [1, 2, 3, 4, 5, 6, 7, 8]
let data = NSData(bytes: &bytes, length: bytes.count)
print(data)
// <01020304 05060708>
print(swapUInt16Data(data))
// <02010403 06050807>

Update for Swift 3: The generic withUnsafeMutableBytes() methods allows to obtain a UnsafeMutablePointer<UInt16> to the bytes and modify them directly:

func swapUInt16Data(data : Data) -> Data {
    var mdata = data // make a mutable copy
    let count = data.count / MemoryLayout<UInt16>.size
    mdata.withUnsafeMutableBytes { (i16ptr: UnsafeMutablePointer<UInt16>) in
        for i in 0..<count {
            i16ptr[i] =  i16ptr[i].byteSwapped
        }
    }
    return mdata
}

Example:

let data = Data(bytes: [1, 2, 3, 4, 5, 6, 7, 8])
print(data as NSData) // <01020304 05060708>

let swapped = swapUInt16Data(data: data)
print(swapped as NSData) // <02010403 06050807>

Update for Swift 5, (fixing a deprecated warning):

func swapUInt16Data(data : Data) -> Data {
    var mdata = data // make a mutable copy
    let count = data.count / MemoryLayout<UInt16>.size
    mdata.withUnsafeMutableBytes { ptr in
        let i16ptr = ptr.assumingMemoryBound(to: UInt16.self)
        for i in 0..<count {
            i16ptr[i] =  i16ptr[i].byteSwapped
        }
    }
    return mdata
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Got this error: "Cannot invoke 'reverse' with an argument list of type '(UnsafeMutableBufferPointer)'" on line var reversedBytes = reverse(bytes). – Marco Almeida May 12 '15 at 15:49
  • Well the first code I get that error and can't compile. The second code works, but does not correctly byte reverse because it reverses like backwards and what I want it if raw NSData output is 45 F4 67 C3, then I want: F4 45 C3 67. I think this is littleEndian. – Marco Almeida May 12 '15 at 15:55
  • @Marco: Strange, it worked in my XCode 6.3.1. But I will double-check when I am at my computer again. – Endianness conversion is a different thing, that wasn't obvious from your question. – Martin R May 12 '15 at 15:56
  • @MartinR, where is the reverse() function defined? And documented? It's a function that takes an `UnsafeMutableBufferPointer` and returns another `UnsafeMutableBufferPointer` containing the reversed byte-stream? – Duncan C May 12 '15 at 16:00
  • Sorry @Sulthan and Martin R. I have edited the question. – Marco Almeida May 12 '15 at 16:15
  • @DuncanC: Unsafe(Mutable)BufferPointer conforms to CollectionType, that's why it can be reverse()'ed. The result is an *array* of the corresponding element type. – Martin R May 12 '15 at 16:57
  • 1
    Martin, thanks for explaining that. Now can you explain where you found byteSwapped? I can't find it in the SwiftDoc.org page the Swift standard library reference, or either of the Swift iBooks. The only way I can find it is to put it in source code in Xcode and command-click on it. I see that it's a computed property defined on UInt16, but I have never heard of it before today. Where do you learn about all this stuff?!? – Duncan C May 12 '15 at 17:07
  • @DuncanC actually it's on the page http://swiftdoc.org/type/UInt16/ at the instance variables section. – Dániel Nagy May 12 '15 at 20:07
  • @MartinR Can you please update your great answer to Swift3 and with the "Data" object? (not NSData) Simple conversion tries didn't work – MatanGold Nov 27 '16 at 09:14
  • @MatanGold: Done :) – Martin R Nov 27 '16 at 09:22
  • @MartinR I have a problem when I try to swap odd number of bytes. I have Data object of length 19 and after the swap I get 18 (dropping the last one) - Is there something can be done? (Here's Hex rep. of the odd data: "AA AA 11 12 0D 00 00 00 01 00 00 02 00 04 00 AC F0 44 3F") – MatanGold Nov 27 '16 at 12:41
  • @MatanGold: Yes, the above code assumes that the number of bytes is even. What result would you expect for the last byte in your case? – Martin R Nov 27 '16 at 12:42
  • @MartinR just leave it as it is (This will keep original data length) – MatanGold Nov 27 '16 at 12:44
  • @MatanGold: See update. The last version was actually wrong, it modified the first values only. – Martin R Nov 27 '16 at 14:04
  • @MartinR Perfect! Gr8! – MatanGold Nov 27 '16 at 15:38
  • Thanx @MartinR, It Does Work for me, my only problem is it send a deprecated warning. "withUnsafeMutableBytes' is deprecated: use `withUnsafeMutableBytes(_: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R` instead". kindly can you write another update for swift 5 too, thanx again and good job. – Ahmed El-Bermawy May 11 '23 at 14:05
  • 1
    @AhmedEl-Bermawy: Updated for Swift 5. – Martin R May 11 '23 at 15:26
  • @MartinR, Fantastic Work. – Ahmed El-Bermawy May 12 '23 at 00:32
3

CoreFoundation has CFSwapInt32BigToHost and CFSwapInt32LittleToHost and other swap functions.

In swift3 it looks like this

struct FileHeader {
    var magicNumber: UInt32 = 0
    var count: UInt32 = 0
    var width: UInt32 = 0
    var height: UInt32 = 0

    static func create(data: Data) -> FileHeader {
        let structSize = MemoryLayout<FileHeader>.size
        assert(data.count >= structSize)
        var result = FileHeader()
        let nsdata = data as NSData
        nsdata.getBytes(&result, range: NSRange(location: 0, length: structSize))
        result.magicNumber = CFSwapInt32BigToHost(result.magicNumber)
        result.count = CFSwapInt32BigToHost(result.count)
        result.width = CFSwapInt32BigToHost(result.width)
        result.height = CFSwapInt32BigToHost(result.height)
        return result
    }
}
neoneye
  • 50,398
  • 25
  • 166
  • 151
  • 1
    Copying the `data` to a `FileHeader` structure can be simplified to `var result: FileHeader = data.withUnsafeBytes { $0.pointee }` without the need for an intermediate `NSData` (compare http://stackoverflow.com/a/38024025/1187415). – Byte swapping can alternatively be done with "native" Swift methods, e.g. `result.magicNumber = UInt32(bigEndian: result.magicNumber)`. – Martin R Nov 27 '16 at 09:28
0

For someone may want to restrict the byte pattern, it would be a solution:

func swap<U:IntegerType>(data:NSData,_ :U.Type) -> NSData{
    var length = data.length / sizeof(U)
    var bytes = [U](count: length, repeatedValue: 0)
    data.getBytes(&bytes, length: data.length)
    // since byteSwapped isn't declare in any protocol, so we have do it by ourselves manually.
    var inverse = bytes.enumerate().reduce([U](count: length, repeatedValue: 0)) { (var pre, ele) -> [U] in
        pre[length - 1 - ele.index] = ele.element
        return pre
    }
    return NSData(bytes: inverse, length: data.length)
}

for example:

swap(data:data,UInt8.self)
//before <0c20207b 17> 
//after <177b2020 0c>

swap(data:anotherData,UInt16.self)
//before <8e004c01 84008f05 0701>
//after <07018f05 84004c01 8e00>
MatthewLuiHK
  • 691
  • 4
  • 10