Apple recently open sourced swift-protobuf which is a plugin of Protocol Buffers for swift language. Protocol Buffers in Swift enables us to have type safety, make API faster and unify schema documentation of structured data. I had a chance to use swift-protobuf in my project and thought that there are many benefits for us, so I would like to share my thoughts.
I also created a repository which has sample server/client app with Protocol Buffers. Please take a look here if you're interested in what implementation looks like.
Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
https://developers.google.com/protocol-buffers/
Protocol Buffers is developed by Google in 2008 and it's been used internally, but they released it as open source project.
https://en.wikipedia.org/wiki/Protocol_Buffers
It's also used in gRPC, it generates Objective-C source code though.
http://www.grpc.io/docs/quickstart/objective-c.html
Most of major languages are supported. For example C++, Go, Java, Python, Ruby, C#, Objective-C, Javascript, PHP and of course Swift. You can see more details here.
Schema of data structure should be defined with message type in .proto
file, so that swift-protobuf can generate swift source code.
Here is an example of token.proto
file.
message Token {
string accessToken = 1;
}
// GET - Response
message GetTokenResponse {
Token token = 1;
}
// POST - Request
message PostTokenRequest {
string accessToken = 1;
}
// POST - Response
message PostTokenResponse {
Token token = 1;
}
If you run protoc
command, it compilies token.proto
file and generates token.pb.swift
file and you will see struct
of Token
which has all of properties in Token
message you defined in .proto
file. The Token
conforms to SwiftProtobuf.Message
protocol. It has serializeProtobuf()
, serializeJSON()
, init(protobuf:)
, init(json:)
methods which allows you to serialize/deserialize value between Data and protobuf type.
Here is an example of token.pb.swift
file.
struct Token: SwiftProtobuf.Message {
var accessToken: String = ""
init() {}
}
struct GetTokenResponse: SwiftProtobuf.Message {
init() {}
var token: Token {...}
}
struct PostTokenRequest: SwiftProtobuf.Message {
var accessToken: String = ""
init() {}
}
struct PostTokenResponse: SwiftProtobuf.Message {
init() {}
var token: Token {...}
}
Also there are HTTP request and response type for API call along with Token
sturct. You can simply serialize PostTokenRequest
type by serializeProtobuf()
method and set it to request body or deserialize GetTokenResponse
or PostTokenResponse
by init(protobuf:)
method.
Every request or response has their type. It's type safety works well in Swift!
Proto type | Swift type |
---|---|
int32 | Int32 |
sint32 | Int32 |
sfixed32 | Int32 |
uint32 | UInt32 |
fixed32 | UInt32 |
int64 | Int64 |
sint64 | Int64 |
sfixed64 | Int64 |
uint64 | UInt64 |
fixed64 | UInt64 |
bool | Bool |
float | Float |
double | Double |
string | String |
bytes | Data |
See more detailed info here
You can set namespace with package
specifier, set access control of struct
and change property name for specific language.
The only definition you'll need is .proto
file. It generates same output for each language, so it won't happen that iOS and Android unexpectedly has type mismatch of property. There also won't be any confusion from undocumented information of schema.
If you use protoc-gen-doc
plugin, you can generate HTML or Markdown documentations from your comments in .proto
file.
You can write type-safe implementation for API call since every single request and response has their type. You can also express state elegantly with enum. Type safety enables us to type check at compile time so that it can save some amount of time to solve unexpected issues during runtime.
Serializing and deserializing methods are already implemented in swift-protobuf since they know what binary format is, so mapping your struct between data and protobuf type works like a charm.
To call API with Protocol Buffers, you set application/protbuf
to Content-Type
and Accept
in HTTP headers and set serialized data to HTTP body. That's it! It's much simpler with Protocol Buffers. You don't need object mapping library or network layer library any more.
Binary data size gets smaller with awesome binary format mechanism so that API request and response gets faster. Encoding and Decoding are also lightweight compare to JSON
.
Please see more detailed info here
Versioning handles backward compatibility well. If you add new field in server side, it will be treated as optional value in client side. However, if you change type of existing field or numbered field, it'll crash your app. Make sure that you should be careful about them.
Binary data is not key value like JSON, so it's hard to understand at a glance. For that case, you can print log as String
by using String.init(data:encoding:)
, or accept JSON
instead for debug usage only.
swift-protobuf is still pre-released version, so it has breaking changes a lot. Make sure that you watch some activities on their repository or you can contribute to swift-protobuf since it's open source.
It's likely to happen compile errors because of version conflict of Swift between your app and swift-protobuf plugin. It's good to know during compile at least, but you have to choose appropriate version of plugin in terms of Swift usage. This issue is not specific one for swift-protobuf. It's more like a general issue for auto-generate tool, but you should pay extra attention.
Javascript seems like that it's not good at handling binary data, so it might be better to use JSON for some case.
I guess it's too early to evaluate swift-protobuf yet, but I believe that it has so many benefits and potentials, especially type safety. You can prevent any future mistakes at compile time, not runtime. It will be best fit to Swift. It's definitely worth a try!
If you're interested in more details, take a look at sample app here as well.
https://developers.google.com/protocol-buffers/
https://github.com/apple/swift-protobuf
https://github.com/google/protobuf
http://www.grpc.io/docs/
https://github.com/estan/protoc-gen-doc