Swift - Writing a Byte Stream to File

Swift - writing a byte stream to file

I would suggest using NSFileHandle.

I tested like this. I started with a file ~/Desktop/test.txt containing the word "testing". I then ran this code:

    let s = "12345"
let d = s.dataUsingEncoding(NSASCIIStringEncoding)!
let path = ("~/Desktop/test.txt" as NSString).stringByExpandingTildeInPath
if let fh = NSFileHandle(forWritingAtPath: path) {
fh.seekToEndOfFile()
fh.writeData(d)
fh.closeFile()
}

The result was that the file now contained

testing12345

A hex dump revealed that the underlying bytes were:

74 65 73 74 69 6E 67 31 32 33 34 35

I believe that's what you said you wanted to achieve.

Also, one further commment:

The best result I can obtain is <3132333435>

It sounds here as if the problem is merely that you don't know how to read the console output. The < and > are not really in the file; they are just part of the console representation of data. It would be better to use BBEdit / TextWrangler or a dedicated hex dumper to see the actual byte of the file.

how to write bytes data in file swift

You can use this function to write data. Code is self explanatory, but I tried to make it more clear.

func writeToFile(data: Data, fileName: String){
// get path of directory
guard let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
return
}
// create file url
let fileurl = directory.appendingPathComponent("\(fileName).txt")
// if file exists then write data
if FileManager.default.fileExists(atPath: fileurl.path) {
if let fileHandle = FileHandle(forWritingAtPath: fileurl.path) {
// seekToEndOfFile, writes data at the last of file(appends not override)
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
}
else {
print("Can't open file to write.")
}
}
else {
// if file does not exist write data for the first time
do{
try data.write(to: fileurl, options: .atomic)
}catch {
print("Unable to write in new file.")
}
}

}

Does swift have a protocol for writing a stream of bytes?

This is something the Swift docs are quiet about and I wanted to know more about so I looked into it.

There is a protocol, it's called Streamable:

protocol Streamable {
func writeTo<Target : OutputStream>(inout target: Target)
}

OutputStream:

protocol OutputStream {
func write(string: String)
}

write allows an object to be written to.

String conforms to both, making it easy to write to and from:

var target = String()
"this is a message".writeTo(&target)
println(target)
// this is a message

Writing to a file:

var msg = "this will be written to an output file"
msg.writeToFile("output.txt", atomically: false, encoding: NSUTF8StringEncoding, error: nil)
// creates 'output.txt' in the same folder as the executable

There is also writeToUrl.

I assume those functions are all built on top of Cocoa streams, which have similar functionality:

var os = NSOutputStream(toFileAtPath: "output.txt", append: true)
os.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)

var msg = "a truly remarkable message"
var ptr:CConstPointer<UInt8> = msg.nulTerminatedUTF8

os.open()
os.write(ptr, maxLength: msg.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
os.close()

How to read and write bits in a chunk of memory in Swift

If you're looking for low level code you'll need to use UnsafeMutableRawPointer. This is a pointer to a untyped data. Memory is accessed in bytes, so 8 chunks of at least 8 bits. I'll cover multiples of 8 bits first.

Reading a File

To read a file this way, you need to manage file handles and pointers yourself. Try the the following code:

// Open the file in read mode
let file = fopen("/Users/joannisorlandos/Desktop/ownership", "r")

// Files need to be closed manually
defer { fclose(file) }

// Find the end
fseek(file, 0, SEEK_END)
// Count the bytes from the start to the end
let fileByteSize = ftell(file)
// Return to the start
fseek(file, 0, SEEK_SET)

// Buffer of 1 byte entities
let pointer = UnsafeMutableRawPointer.allocate(byteCount: fileByteSize, alignment: 1)

// Buffer needs to be cleaned up manually
defer { pointer.deallocate() }

// Size is 1 byte
let readBytes = fread(pointer, 1, fileByteSize, file)
let errorOccurred = readBytes != fileByteSize

First you need to open the file. This can be done using Swift strings since the compiler makes them into a CString itself.

Because cleanup is all for us on this low level, a defer is put in place to close the file at the end.

Next, the file is set to seek the end of the file. Then the distance between the start of the file and the end is calculated. This is used later, so the value is kept.

Then the program is set to return to the start of the file, so the application starts reading from the start.

To store the file, a pointer is allocated with the amount of bytes that the file has in the file system. Note: This can change inbetween the steps if you're extremely unlucky or the file is accessed quite often. But I think for you, this is unlikely.

The amount of bytes is set, and aligned to one byte. (You can learn more about memory alignment on Wikipedia.

Then another defer is added to make sure no memory leaks at the end of this code. The pointer needs to be deallocated manually.

The file's bytes are read and stored in the pointer. Do note that this entire process reads the file in a blocking manner. It can be more preferred to read files asynchronously, if you plan on doing that I'll recommend looking into a library like SwiftNIO instead.

errorOccurred can be used to throw an error or handle issues in another manner.

From here, your buffer is ready for manipulation. You can print the file if it's text using the following code:

print(String(cString: pointer.bindMemory(to: Int8.self, capacity: fileByteSize)))

From here, it's time to learn how to read manipulate the memory.

Manipulating Memory

The below demonstrates reading byte 20..<24 as an Int32.

let int32 = pointer.load(fromByteOffset: 20, as: Int32.self)

I'll leave the other integers up to you. Next, you can alos put data at a position in memory.

pointer.storeBytes(of: 40, toByteOffset: 30, as: Int64.self)

This will replace byte 30..<38 with the number 40. Note that big endian systems, although uncommon, will store information in a different order from normal little endian systems. More about that here.

Modifying Bits

As you notes, you're also interested in modifying five or ten bits at a time. To do so, you'll need to mix the previous information with the new information.

var data32bits = pointer.load(fromByteOffset: 20, as: Int32.self)
var newData = 0b11111000

In this case, you'll be interested in the first 5 bits and want to write them over bit 2 through 7. To do so, first you'll need to shift the bits to a position that matches the new position.

newData = newData >> 2

This shifts the bits 2 places to the right. The two left bits that are now empty are therefore 0. The 2 bits on the right that got shoved off are not existing anymore.
Next, you'll want to get the old data from the buffer and overwrite the new bits.
To do so, first move the new byte into a 32-bits buffer.

var newBits = numericCast(newData) as Int32

The 32 bits will be aligned all the way to the right. If you want to replace the second of the four bytes, run the following:

newBits = newBits << 16

This moves the fourth pair 16 bit places left, or 2 bytes. So it's now on position 1 starting from 0.

Then, the two bytes need to be added on top of each other. One common method is the following:

let oldBits = data32bits & 0b11111111_11000001_11111111_11111111
let result = oldBits | newBits

What happens here is that we remove the 5 bits with new data from the old dataset. We do so by doing a bitwise and on the old 32 bits and a bitmap.

The bitmap has all 1's except for the new locations which are being replaced. Because those are empty in the bitmap, the and operator will exclude those bits since one of the two (old data vs. bitmap) is empty.

AND operators will only be 1 if both sides of the operator are 1.

Finally, the oldBits and the newBits are merged with an OR operator. This will take each bit on both sides and set the result to 1 if the bits at both positions are 1.
This will merge successfully since both buffers contain 1 bits that the other number doesn't set.

Overwriting the first N bytes in a file in Swift

Use FileHandle.

let handle = FileHandle(forWritingTo: outputURL)
handle.seek(toFileOffset: 0)
handle.write("OOPS".data(using: .utf8))
handle.closeFile()

I leave it to the reader to deal with handling optionals and needing to catch errors.



Related Topics



Leave a reply



Submit