SureshJoshi.com ▼

Swift-ly Streaming Protocol Buffers


2016-04-19

I feel my bias towards Android programming has really made itself shown over the past 6 months. I’m pretty comfortable saying that Android is a much better platform to develop on than iOS (keeping in mind that I do the majority of my development on Android - with brief forays into iOS, which quickly make me want to go back to Android). Swift was a breath of fresh air, after the mess that was Objective C, but there are still so many problems - it’s hard to bring myself back to iOS land time and time again…

Self-Improvement Time!

Anyways, this post is the beginning of my bias-removal system. I’m going to slowly, but surely, aim for something closer to a 70/30 split in my mobile development posts (rather than 99/1). Predominantly Android - but I’m getting more and more Swift and iOS requests, so I can’t leave those behind.

This Swift-based Protocol Buffers post will be more action than talk, mostly because I did all my talking in my previous two articles (here and here). What I’ve done here is to take my length-delimited Android Protobuf implementation as do as straight-forward an equivalent example as possible in Swift, while not ignoring some of Swift’s upsides.

Protobuf-Swift

First, I need to talk about how I started the project off.

As there is no native Google Swift Protocol Buffer implementation, I tried out this one by Alexey. He’s got another Protobuf implementation in Objective-C which a colleague has tried, and it worked - so I gave the Swift one a shot.

Follow the Github instructions carefully, as the Swift protoc compiler is built/installed from there. Once you have the protoc generator plugin, just compile your .proto file, and put the new Swift class somewhere you can use it.

Heads up, it will have an ugly filename if you have namespacing in your .proto file (which I usually do - mine was called ‘Sureshjoshi.pb.Sample.swift’). I tried using Alexey’s custom ‘options’ to fix this, but protoc wouldn’t generate a file with them, and I gave up very quickly without a fight.

To The Code!

A great example of my favourite functionality in Swift, which I really wish existed in Java (if this functionality does exist, and nobody told me about it - I’m going to lose it). That functionality is… Extensions! Actually, maybe I like some of Swift because they’ve ripped off a lot of C# functionality that I like? That’s not a negative statement… Developing a language/library based on amazing features in other languages is a fantastic idea (see: Reactive Extensions).

For my Android implementation - I created a utility class with several static functions. I don’t particularly see anything wrong with this, but I wouldn’t call it an ‘elegant’ implementation.

I started off with something similar in Swift, then decided I didn’t like how it felt… Or maybe it’s because I don’t like how Swift handles generics/templating… No well-defined reason for that, it just feels weird.

Anyways, I decided to see how those methods looked as extensions, and I gotta be honest, I think they’re much better there. While I don’t think I’m necessarily hanging them off the correct objects, I’m limited by the generated protocols and classes I’m given.

Caveat to the above statement: I really don’t like how the ‘readAllDelimitedFrom’ turned out. Feels sloppy to have a returned ‘Self’ array sitting on top of that protocol.

Anyways, to the code! As usual, my code is fully available on Github: https://github.com/sureshjoshi/swift-streaming-protobuf-example

import ProtocolBuffers

extension GeneratedMessage {
    func writeDelimitedTo(outputStream: NSOutputStream) throws -> Int {
        var messageSize = Int32(littleEndian: self.serializedSize())
        withUnsafePointer(&messageSize) {
            outputStream.write(UnsafePointer($0), maxLength: sizeofValue(messageSize))
        }
        try self.writeToOutputStream(outputStream)
        return Int(messageSize + 4)
    }
}

extension Array where Element:GeneratedMessage {
    func writeDelimitedTo(outputStream: NSOutputStream) throws -> Int {
        var writtenSize = 0;
        for message in self {
            writtenSize += try message.writeDelimitedTo(outputStream)
        }
        return writtenSize
    }
}

extension GeneratedMessageProtocol {
    static func readDelimitedFrom(inputStream: NSInputStream) throws -> Self {
        var sizeBuffer: [UInt8] = [0,0,0,0]
        inputStream.read(&sizeBuffer, maxLength: sizeBuffer.count)
        let data = NSData(bytes: sizeBuffer, length: sizeBuffer.count)
        let messageSize = Int(data.uint32.littleEndian)

        var buffer = Array<UInt8>(count: messageSize, repeatedValue: 0)
        inputStream.read(&buffer, maxLength: messageSize)

        let messageData = NSData(bytes: buffer, length:messageSize)

        return try self.parseFromData(messageData)
    }

    static func readAllDelimitedFrom(inputStream: NSInputStream) throws -> [Self] {
        var messages = [Self]()
        while inputStream.hasBytesAvailable {
            try messages.append(self.readDelimitedFrom(inputStream))
        }
        return messages
    }

}

The heart (and soul) of my code was described above. It’s the equivalent to the WireUtils and is located in ProtocolBuffersExtensions.swift. I can probably clean up some of those ugly back and forths to NSData if I put my mind to it, but overall, the structure is there.

Sample App

I created a few unit tests on the above code, as well as mimicking the Android example app (using Charts, which is the port of MPAndroidChart). The only change with any substance is that I switched from a file stream in Android to an in-memory stream in iOS.

There is no purpose to this, other than the fact that I was doing file streaming benchmarking in Android - and actually cared about specifically using a file stream.

Swift-Sample-App

No COBS

I won’t be replicating the COBS streaming Protobuf example in Swift or iOS, because Apple makes it a nightmare to connect hardware to a phone using Bluetooth Classic or USB (need to register for that MFi program - which I’ve done, and hated every minute of it). As a result, I never use COBS on iOS.

Feature Photo credit: Billy Lindblom / Foter / CC BY