Search code examples
iosswiftnsdata

Splitting or Chunking NSData on specific value(s) in swift


I'm trying to figure out how to "chunk" a NSData on a specific value.

Input Data

7E 55 33 22 7E 7E 22 AE BC 7E 7E AA AA 00 20 00 22 53 25 A3 4E 7E

Output Data

Returns an array of 3 elements of type [NSData] where the elements are:

  • 55 33 22
  • 22 AE BC
  • AA AA 00 20 00 22 53 25 A3 4E

Where I'm at

I know I can do something like:

var ptr = UnsafePointer<UInt8>(data.bytes)
var bytes = UnsafeBufferPointer<UInt8>(start: ptr, count: data.length)

And I guess iterate through doing a comparison similar to:

bytes[1] == UInt8(0x7E)

And I guess build up "ranges" but I was wondering if there is a better way to go about this?

Code Stub

private fund chunkMessage(data: NSData) -> [NSData] {
  var ptr = UnsafePointer<UInt8>(data.bytes)
  var bytes = UnsafeBufferPointer<UInt8>(start: ptr, count: data.length)
  var ret = []

 // DO SOME STUFF

  return ret as! [NSData];

}

Solution

  • There are probably many possible solutions. A straight-forward way, using NSData methods, would be

    func chunkMessage(data: NSData, var separator : UInt8) -> [NSData] {
        let sepdata = NSData(bytes: &separator, length: 1)
        var chunks : [NSData] = []
    
        // Find first occurrence of separator:
        var searchRange = NSMakeRange(0, data.length)
        var foundRange = data.rangeOfData(sepdata, options: nil, range: searchRange)
        while foundRange.location != NSNotFound {
            // Append chunk (if not empty):
            if foundRange.location > searchRange.location {
                chunks.append(data.subdataWithRange(NSMakeRange(searchRange.location, foundRange.location - searchRange.location)))
            }
            // Search next occurrence of separator:
            searchRange.location = foundRange.location + foundRange.length
            searchRange.length = data.length - searchRange.location
            foundRange = data.rangeOfData(sepdata, options: nil, range: searchRange)
        }
        // Check for final chunk:
        if searchRange.length > 0 {
             chunks.append(data.subdataWithRange(searchRange))
        }
        return chunks
    }
    

    As already suggested in the comments, you could work with Swift arrays instead. Here is a possible implementation:

    func chunkMessage(data: NSData, separator : UInt8) -> [[UInt8]] {
    
        let bytes = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes), count: data.length)
    
        // Positions of separator bytes:
        let positions = filter(enumerate(bytes), { $1 == separator } ).map( { $0.0 } )
    
        // Non-empty ranges between the separator bytes:
        let ranges = map(Zip2([-1] + positions, positions + [bytes.count])) {
            (from : Int, to : Int) -> (Int, Int) in
            (from + 1, to - from - 1)
            }.filter( { $1 > 0 } )
    
        // Byte chunks between the separator bytes:
        let chunks = map(ranges) {
            (start: Int, count : Int) -> [UInt8] in
            Array(UnsafeBufferPointer(start: bytes.baseAddress + start, count: count))
        }
    
        return chunks
    }
    

    I leave it to you to test which one performs better :)