swiftunsafemutablepointer

What is the correct way in swift to wrap a segment of a UInt8 array as a String?


I have some raw data processing to do in an iPhone app. Strings always come out of an extremely large underlying byte array, so I want to be able to pull strings out of the array without triggering out of memory issues.

I can see a String(bytesNoCopy: ...) in the documentation, is this what I want, and how exactly is it supposed to be used?

Assuming an array of uint8 called data and index is a number which shows where the string is inside the array.

var myData:[UInt8] = [
    4,                // String 1 length
    65,66,67,68,0,    // String 1 data
    4,                // String 2 length
    69,70,71,71,0     // String 2 data
]

var index = 0
let string1 = readString(&myData, &index)
let string2 = readString(&myData, &index)
print(string1, string2)

// Read a string located at a specific
// position in a byte array, and increment
// the pointer into the array into the next
// position
func readString(_ data:inout [UInt8], _ index:inout Int)  -> String {
    // Read string length out of data array
    let l = Int(readUInt8(&data, &index))

    // Read string out of data array without copy
    let s = String(
        bytesNoCopy: UnsafeMutableRawPointer(data + index), // <-- what goes here??
        length: l,
        encoding: .utf8,
        freeWhenDone: false)
    index = index + l
    if s == nil {
        return ""
    }
    return s!
}

// Read a byte as an integer from a 
// data array, and increment the pointer into
// the data array to the next position.
func readUInt8(_ data:inout [UInt8], _ x:inout Int)  -> UInt8 {
    let v = data[x]
    x = x + 1
    return v
}

NOTE: This question is updated to include sample data, and renamed the variable x to index to make it clearer that the question was asking how to create a string from a segment of a byte array.


Solution

  • Here's how you can do try this -

    import Foundation
    
    func readString(_ data: inout [UInt8], _ x: inout Int) -> String {
        let l = 4
        var slice: ArraySlice<UInt8> = data[x..<x+l] // No copy, view into existing Array
        x += l
        
        return slice.withUnsafeBytes({ pointer in
            // No copy, just making compiler happy (assumption that it is bound to UInt8 is correct
            if let bytes = pointer.baseAddress?.assumingMemoryBound(to: UInt8.self) {
                return String(
                    bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes), // No copy
                    length: slice.count,
                    encoding: .utf8,
                    freeWhenDone: false
                ) ?? ""
            } else {
                return ""
            }
        })
    }
    

    Test

    var a: [UInt8] = [
        65, 66, 67, 68, 
        69, 70, 71, 72
    ]
    var x = 0
    
    let test1 = readString(&a, &x)
    print("test1 : \(test1)")
    // test1 : ABCD
    
    let test2 = readString(&a, &x)
    print("test2 : \(test2)")
    // test2 : EFGH