Write a CircularBuffer (swift-nio) to a file in Swift using OutputStream?

467 views Asked by At

I am trying to use a CircularBuffer<UInt8> from SwiftNIO to store data and once the buffer is almost full dump the contents to a file using an OutputStream. Unfortunately, the OutputStream.write() method takes UnsafePointer as an argument, while the CircularBuffer can output UnsafeBufferPointer . Is there a way to convert CircularBuffer to UnsafePointer?

I have tried to extend CircularBuffer with the following code that I am using with success to convert structs to Byte arrays as it was suggested that CircularBuffer is in fact a struct, but I am getting garbage in my output file:

extension CircularBuffer {
    func toBytes() -> [UInt8] {
        let capacity = MemoryLayout<Self>.size
        var mutableValue = self
        return withUnsafePointer(to: &mutableValue) {
            return $0.withMemoryRebound(to: UInt8.self, capacity: capacity) {
                return Array(UnsafeBufferPointer(start: $0, count: capacity))
            }
        }
    }
}

Any thoughts?

2

There are 2 answers

2
Martin R On BEST ANSWER

CircularBuffer is a struct with an internal ContiguousArray for the element storage. ContiguousArray is also a struct, with internal pointers to the actual element storage.

Your current code produce garbage because it returns the memory representation of the struct CircularBuffer itself, and not the bytes of the elements which it represents.

As a collection, CircularBuffer has a withContiguousStorageIfAvailable() method which calls a closure with a pointer to the element storage if such contiguous storage exists. The closure is called with an UnsafeBufferPointer argument from which you can obtain the baseAddress:

var buffer: CircularBuffer<UInt8> = ...
let os: OutputStream = ...
// ...
let amountWritten =  buffer.withContiguousStorageIfAvailable {
    os.write($0.baseAddress!, maxLength: $0.count)
}

But there is a problem: CircularBuffer just inherits the default implementation from Sequence which returns nil without calling the closure. This is a known issue. So the above code would compile, but not work.

A simple way (at the cost of copying the contents) would be to use that you can initialize an array from a collection:

var buffer: CircularBuffer<UInt8> = ...
let os: OutputStream = ...
// ...
let contents = Array(buffer)
let amountWritten = os.write(contents, maxLength: contents.count)
0
matt On

You can also cycle through the elements of the buffer one at a time, though that would probably be quite inefficient:

    var cb = CircularBuffer<UInt8>()
    cb.append(contentsOf:[1,2,3])
    cb.append(contentsOf:[4,5,6])
    let stream = OutputStream.toMemory()
    stream.open()
    for var i in cb {
        stream.write(&i, maxLength:1)
        print(i)
    }
    stream.close()
    print(stream.property(forKey: .dataWrittenToMemoryStreamKey) as Any)