I'm using Network Framework to transfer files between 2 devices. The "secondary" device sends file requests to the "primary" device, and the primary sends the files back. When the primary gets the request, it responds like this:
do {
let data = try Data(contentsOf: filePath)
let priSecDataFilePacket = PriSecDataFilePacket(fileName: filename, dataBlob: data)
let jsonData = try JSONEncoder().encode(priSecDataFilePacket)
let message = NWProtocolFramer.Message(priSecMessageType: PriSecMessageType.priToSecDataFile)
let context = NWConnection.ContentContext(identifier: "TransferUtility", metadata: [message])
connection.send(content: encodedJsonToSend, contentContext: context, isComplete: true, completion: .idempotent)
} catch {
print("\(error)")
}
It works great, even for hundreds of file requests. The problem arises if some files being requested are extremely large, like 600MB. You can see the memory speedometer on the primary quickly ramp up to the yellow zone, at which point iOS kills the app for high memory use, and you see the Jetsam log.
I changed the code to skip JSON encoding the binary file as a test, and that helped a bit, but it still goes too high; the real offender is the step where it loads the 600MB file into the data
var:
let data = try Data(contentsOf: filePath)
If I remark out everything else and just leave that one line, I can still see the memory use spike.
As a fix, I'm rewriting this so the secondary requests the file in 5MB chunks by telling the primary a byte range such as "0-5242880" or "5242881-10485760", and then reassembling the chunks on the secondary once they all come in. So far this seems promising, but it's a fair amount of work.
My question: Does Network Framework have a built-in way to stream those bytes straight from disk as it sends them? So that I could send all the data in one single request without having to load the bytes into memory?
No. When dealing with a large file like this, you have to adopt a streaming approach. That is:
-
Read a chunk of the file off the disk.
-
Send it to connection.
-
When the send completion handler is called, check whether there’s more data to send. If so, repeat the process from step 1.
It’s weird that you’re JSON encoding the data. If you’re dealing with large files, JSON is needlessly inefficient. That doesn’t change the calculus above — you could make the transfer smaller by skipping the encoding but there can always be a file that’s too big — but it does affect your on-the-wire efficiency. You’re transferring one third more bytes than you need to.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"