Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2016-03-04

Swift Code Library: A byte buffer class providing UnsafeBufferPointer access.

The UnsafeBufferPointer is one of the more beautiful pointer types in Swift. It combines the power of a collection to the sleekness of a pointer.

I needed a buffer over which I have to iterate and that I also have to convert into a String for further processing. In addition I need operations to add and remove bytes from it and I don't want use the high level Swift syntax for that. (That way it is easy to incur more overhead than necessary)

Without further ado, here is the code:

class UInt8Buffer {
    
    
    // The buffer area (is allocated during init)
    
    private var area: UnsafeMutablePointer<UInt8>
    
    
    /// The maximum number of bytes that can be contained in this buffer, is fixed during init and cannot be changed afterwards.
    
    let size: Int
    
    
    /// The number of bytes contained in the buffer.
    
    var fill: Int { return _fill }
    private var _fill = 0
    
    
    /// Access to the data in the buffer area.
    /// - Note: Adding or removing data invalidates previously returned buffer pointers.

    var ptr: UnsafeBufferPointer<UInt8> {
        return UnsafeBufferPointer(start: area, count: _fill)
    }
    
    
    /// - Returns: The value of the received data interpreted as an UTF-8 encoded String. Nil if the data could not be converted.
    /// - Note: The returned value is a struct and thus a copy of the data in the buffer.
    
    var stringValue: String {
        return String(bytes: ptr, encoding: NSUTF8StringEncoding) ?? ""
    }
    
    
    /// Creates a new buffer
    
    init(sizeInBytes: Int) {
        size = sizeInBytes
        area = UnsafeMutablePointer<UInt8>.alloc(size)
    }
    
    
    /// Creates a new buffer with the data from the given buffer starting at the byte at startByteOffset and ending with the byte at endByteOffset.
    /// The fill property will be set to the number of bytes. Hence no further data can be added to this buffer without first making room for it.
    
    convenience init(from: UInt8Buffer, startByteOffset start: Int, endByteOffset end: Int) {
        let theSize = end - start + 1
        self.init(sizeInBytes: theSize)
        memcpy(area, from.area + start, theSize)
        _fill = theSize
    }
    
    
    /// Destroys and frees the data area
    
    deinit {
        area.dealloc(size)
    }
    
    
    /// Add the given data to this buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    /// - Returns: True when successful, false when not all data could be added (buffer-full)
    
    func add(data: UInt8Buffer) -> Bool {
        guard _fill < size else { return false }
        let nofBytesToCopy = min((size - _fill), data._fill)
        memcpy(area + _fill, data.area, nofBytesToCopy)
        _fill += nofBytesToCopy
        return nofBytesToCopy == data._fill
    }
    
    
    /// Add the given data to this buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    /// - Returns: True when successful, false when not all data could be added (buffer-full)

    func add(srcPtr: UnsafePointer<UInt8>, length: Int) -> Bool {
        guard _fill < size else { return false }
        let nofBytesToCopy = min((size - _fill), length)
        memcpy(area+_fill, srcPtr, nofBytesToCopy)
        _fill += nofBytesToCopy
        return nofBytesToCopy == length
    }
    
    
    /// Add the given data to this buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    /// - Returns: True when successful, false when not all data could be added (buffer-full)

    func add(buffer: UnsafeBufferPointer<UInt8>) -> Bool {
        return self.add(buffer.baseAddress, length: buffer.count)
    }
    
    
    /// Add the given data to this buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    /// - Returns: True when successful, false when not all data could be added (buffer-full)

    func add(data: NSData) -> Bool {
        return self.add(UnsafePointer<UInt8>(data.bytes), length: data.length)
    }
    
    
    /// Removes the indicated number of bytes from the start of the buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.

    func remove(bytes: Int) {
        let nofBytesToRemove = min(bytes, _fill)
        if nofBytesToRemove == _fill { _fill = 0; return }
        let nofBytesToMove = _fill - nofBytesToRemove
        memcpy(area + nofBytesToRemove, area, nofBytesToMove)
    }
    
    
    /// Removes everything.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    
    func removeAll() {
        _fill = 0
    }

}

The Unix call 'memcpy' is used to do the actual copying. This is a very efficient way of copying the data around.

Update: 2016-03-14 Fixed a problem on stringValue and did away with an extra zero byte.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment