This is a description of how you can use the code generated by the plugin.
Apart from the actual properties themselves, most of this is not explicit in the generated code. Rather, most of this appears in common shared protocols in the runtime library. They're collected here to present a single document for how you can use the generated code.
Messages in the input proto file generate Swift structs in the result. These
structs conform to SwiftProtobuf.Message
and provide Swift properties for every
field, basic information about the message, standard initializers, and
serialization and deserialization methods.
Here is a simple proto3 input file to motivate the example below:
syntax = "proto3";
message Example {
enum E {
DEFAULT = 0;
}
int32 field1 = 1;
repeated string field2 = 2;
}
Here is the API for the struct generated from the above. Note that this includes a lot of methods that come from various protocols and omits some parts of the generated code that are intended purely for internal use by the library:
public struct Example: SwiftProtobuf.Message {
// The generated struct carries constant properties reflecting
// basic information about the message:
public var protoMessageName: String {return "Example"}
// Nested enum and message types are nested in the generated Swift
public enum E: SwiftProtobuf.Enum { ... }
// A public property is created for each field in the proto.
public var field1: Int32 { get set }
public var field2: [String] { get set }
// Default Initializer
public init()
// A convenience factory method for constructing
// immutable objects. You can use it like this:
//
// let e = Example.with {
// $0.field1 = 7
// $0.field2 = ["foo", "bar"]
// }
static public with(_ configurator: (inout Example) -> ());
// Messages can be serialized or deserialized to Data objects
// using protobuf binary format.
// The extension set methods are used by extensible proto2 messages;
// see below for more details.
func serializedData() throws -> Data
init(serializedData: Data) throws {
init(serializedData: Data, extensions: ProtobufExtensionSet? = nil) throws
// Messages can be serialized or deserialized to String objects
// using JSON format.
func jsonUTF8Data() throws -> Data
init(jsonUTF8Data: Data) throws
func jsonString() throws -> String
init(jsonString: String) throws
// Messages can be serialized or deserialized to Protobuf text format:
func serializedText() throws -> String
init(serializedText: String) throws
// These are the generated methods used by all serialization and
// deserialization mechanisms. You should generally not
// call them directly.
public func decodeField(setter: inout ProtobufFieldDecoder, protoFieldNumber: Int) throws -> Bool
public func traverse(visitor: inout ProtobufVisitor) throws
}
func ==(lhs: Example, rhs: Example) -> Bool
You can redefine the following in manually-constructed extensions if you want to override the default generated behavior for any reason:
public var debugDescription: String
public var hashValue: Int
// The == operator is implemented in terms of this method
// so that you can easily override the implementation.
// Although you may override this method, you should never
// call it directly.
public var isEqual(other: Example) -> Bool
Proto enums are translated to Swift enums in a fairly straightforward manner.
The resulting Swift enums conform to the SwiftProtobuf.Enum
protocol which extends
RawRepresentable
with a RawValue
of Int
.
Proto2 enums simply have a case for each enum value. If deserialization encounters an unknown value, that value is either ignored (JSON) or handled as an unknown field (protobuf binary), depending on the particular serialization.
Proto3 enums have an additional UNRECOGNIZED(Int)
case that is used whenever
an unrecognized value is parsed from protobuf serialization or from other
serializations that store integer enum values. JSON serialization with named
enum values drops unrecognize names.
public enum MyEnum: SwiftProtobuf.Enum {
public typealias RawValue = Int
// Case for each value
// Names are translated to a lowerCamelCase convention from
// the UPPER_CASE convention in the proto file:
case default
case other
case andMore
case UNRECOGNIZED(Int) // Only in proto3 enums
// Initializer selects the default value (see proto2 and proto3
// language guides for details.
public init()
public init?(rawValue: Int)
public var rawValue: Int
public var hashValue: Int
public var debugDescription: String
}
Types in the proto file are mapped to Swift types as follows:
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 |
Enums in the proto file generate Int-valued enums in the Swift code.
Groups in the proto file generate Swift structs that conform to SwiftProtobuf.Message
.
Messages in the proto file generate Swift structs that conform to
SwiftProtobuf.Message
.
Note: There is also a SwiftProtobuf._MessageImplementationBase
protocol. You should not refer to that directly; use
SwiftProtobuf.Message
when you need to work with arbitrary groups or
messages.
Proto3 singular fields generate properties of the corresponding type above. These properties are initialized to the appropriate default value as specified in the proto3 documentation:
- Numeric fields are initialized to zero.
- Boolean fields are initialize to false.
- String fields are initialized to the empty string.
- Bytes fields are initialized to an empty Data() object.
- Enum fields are initialized to the default value (the value corresponding to zero, which must be the first item in the enum).
- Message fields are initialized to an empty message of the appropriate type.
Notes: For performance, the field may be initialized lazily, but this is invisible to the user. The property will be serialized if it has a non-default value.
Proto2 optional
fields generate properties of the corresponding
type above. It also generates has
and clear
methods that can be used to
test whether the field has a value or to reset it to it's default.
Proto2 optional
fields with default values If the proto file
specified a default value, that value will appear when the field has
not been set or after it has been cleared.
Proto2 required
fields Required fields behave the same as
optional fields, except that serialization or deserialization may
fail if the field is not provided.
Proto repeated
fields generate simple properties of type Array<T>
where
T is the base type from above. Repeated fields are always initialized to an empty array.
Proto map
fields generate simple properties of type Dictionary<T,U>
where T and U are the respective key and value types from above. Map fields
are always initialized to an empty map.
Oneof fields generate an enum with a case for each associated field plus a
separate None
case. These enums conform to ProtobufOneofEnum
. Every case
except for the None
case has an associated value corresponding to the declared
field.
In addition to the enum itself, the message will contain a read/write property named after the enum which contains the enum value and a separate read/write computed property for each member field of the enum.
Groups act exactly like messages in all respects, except that they cannot be directly serialized on their own.
For most of the proto3 well-known types, the Swift API is exactly what you would expect from the corresponding proto definitions. (In fact, the runtime library version for most of these is simply generated.) For convenience, most of these also have hand-written extensions that expand the functionality with various convenience methods. The variations from the default generated behavior are described below.
Proto Type | Swift Type |
---|---|
google.protobuf.Any | Google_Protobuf_Any |
google.protobuf.Api | Google_Protobuf_Api |
google.protobuf.BoolValue | Google_Protobuf_BoolValue |
google.protobuf.BytesValue | Google_Protobuf_BytesValue |
google.protobuf.DoubleValue | Google_Protobuf_DoubleValue |
google.protobuf.Duration | Google_Protobuf_Duration |
google.protobuf.Empty | Google_Protobuf_Empty |
google.protobuf.FieldMask | Google_Protobuf_FieldMask |
google.protobuf.FloatValue | Google_Protobuf_FloatValue |
google.protobuf.Int64Value | Google_Protobuf_Int64Value |
google.protobuf.ListValue | Google_Protobuf_ListValue |
google.protobuf.StringValue | Google_Protobuf_StringValue |
google.protobuf.Struct | Google_Protobuf_Struct |
google.protobuf.Timestamp | Google_Protobuf_Timestamp |
google.protobuf.Type | Google_Protobuf_Type |
google.protobuf.UInt32Value | Google_Protobuf_UInt32Value |
google.protobuf.UInt64Value | Google_Protobuf_UInt64Value |
google.protobuf.Value | Google_Protobuf_Value |
The Any JSON representation is treated essentially as a separate serialization format. Each message has
TODO
These are customized to produce the expected JSON and Any JSON forms, but are otherwise exactly as you would expect.
The Google_Protobuf_Duration
and Google_Protobuf_Timestamp
structs have
customized JSON and Any JSON handling to match Google's specification. In
particular, JSON serialization will throw an error if the duration is greater
than 315576000000 seconds or if the timestamp is before 0001-01-01T00:00:00Z
or after 9999-12-31T23:59:59.999999999Z
Gregorian proleptic.
The Google_Protobuf_Duration
type also conforms to
ExpressibleByFloatLiteral
; it can be initialized with a double representing
the number of seconds.
There are also overrides for simple arithmetic with durations and timestamps:
func -(lhs: Google_Protobuf_Timestamp, rhs: Google_Protobuf_Timestamp) -> Google_Protobuf_Duration
func -(lhs: Google_Protobuf_Duration, rhs: Google_Protobuf_Duration) -> Google_Protobuf_Duration
public func +(lhs: Google_Protobuf_Duration, rhs: Google_Protobuf_Duration) -> Google_Protobuf_Duration
public func -(operand: Google_Protobuf_Duration) -> Google_Protobuf_Duration
public func -(lhs: Google_Protobuf_Timestamp, rhs: Google_Protobuf_Duration) -> Google_Protobuf_Timestamp
public func +(lhs: Google_Protobuf_Timestamp, rhs: Google_Protobuf_Duration) -> Google_Protobuf_Timestamp
Google_Protobuf_NullValue
is a simple single-value enum.
As expected, Google_Protobuf_Struct
contains a single fields
dictionary
mapping strings to Google_Protobuf_Value
objects. It also conforms to
ExpressibleByDictionaryLiteral
and provides a subscript
for directly
accessing the values by name.
The Google_Protobuf_Value
type implements the expected oneof semantics, with
a separate property for each type of contained object.
Extensions are used to add additional properties to messages defined elsewhere. They are fully supported in proto2 files. They are supported in proto3 only when extending the standard Descriptor type. These are defined in proto files as follows:
/// File sample.proto
syntax="proto2";
message CanBeExtended {
extensions 100 to 200;
}
extend CanBeExtended {
optional int32 extensionField = 100;
}
There are several pieces to the extension support:
- Extensible Messages (such as
CanBeExtended
above) conform toProtobufExtensibleMessage
and define some additional methods needed by the other components. You should not use these methods directly. - Extension properties use Swift
extension
to add new properties to the original message. In many cases, you can simply use the extension properties without understanding any of the other extension machinery. The above example creates a Swift extension ofCanBeExtended
that defines a new propertyextensionField
of typeOptional<Int32>
. - Extension objects are opaque objects that define the extension itself,
including storage and serialization details. Because proto allows extension
names to be reused, these objects appear in the context corresponding to the
context where the proto extension was defined, which generally does not
correspond to that of the message being extended. In the above example, the
extension object would be
CanBeExtended_extensionField
at the file scope. - Extension sets are collections of extension objects. An extension set is
generated by the code generator and included as a static global variable in
every Swift source file generated from a proto file that defines extensions.
Extension sets are provided to the deserialization APIs to define what
extension fields the decoder should recognize. In the above example, the
generated extension set would be
Sample_Extensions
.
/// ProtobufExtensionSet implements a subset of the Set API.
public struct ProtobufExtensionSet: ExpressibleByArrayLiteral {
public typealias Element = ProtobufMessageExtension
public init()
init(arrayLiteral: Element...)
public subscript(messageType: ProtobufMessage.Type, protoFieldNumber: Int) -> ProtobufMessageExtension?
public func fieldNumberForJson(messageType: ProtobufJSONMessageBase.Type, jsonFieldName: String) -> Int?
public mutating func insert(_: Element)
public mutating func insert(contentsOf: [Element])
public mutating func union(_: ProtobufExtensionSet) -> ProtobufExtensionSet
}
The following describes how each construct in a .proto
file gets translated
into Swift language constructs:
Files: Each input .proto
file generates a single output file with the
.proto
extension replaced with .pb.swift
.
Messages: Each input message generates a single output struct that conforms
to the ProtobufMessageType
protocol. Small leaf messages (those that don't
have message-typed fields) generate simple structs. Non-leaf messages use a
private copy-on-write backing class to provide full value semantics while also
supporting recursive data structures. Nested messages generate nested struct
types so that
package quux;
message Foo {
message Bar {
int32 baz = 1;
}
Bar bar = 1;
}
will be compiled to a structure like the following
public struct QuuxFoo {
// ...
public struct Bar {
// ...
var baz: Int32 {get set}
// ...
}
var bar: Bar {get set}
// ...
}
which you can use as follows:
var foo = QuuxFoo()
foo.bar.baz = 77
Groups: Each group within a message generates a nested struct. The group
struct implements the ProtobufGroupType
protocol which differs from
ProtobufMessageType
primarily in how it handles serialization and
deserialization (you do not normally need to be aware of this). Note that
groups are deprecated and only available with the older 'proto2' language
dialect.
Binary Serialization and Deserialization: You can serialize to a Data
using the serializeProtobuf()
method or deserialize with the corresponding
initializer:
init(protobuf: Data) throws
func serializeProtobuf() throws -> Data
Protobuf binary serialization can currently only fail if the data includes Any fields that were decoded from JSON format. See below for details.
Unrecognized fields are preserved through decoding/encoding cycles for proto2 messages. Unrecognized fields are dropped for proto3 messages.
JSON Serialization and Deserialization: Similarly, JSON serialization is
handled by jsonString()
which returns a String
with the result and
jsonUTF8Data()
which returns a Data
containing UTF8-encoded text.
Deserialization is handled by the corresponding initializer:
init(jsonUTF8Data: Data) throws
func jsonUTF8Data() throws -> Data
init(jsonString: String) throws
func jsonString() throws -> String
JSON serialization can fail if there are Any fields that were decoded from binary protobuf format, or if you abuse the well-known Timestamp, Duration, or FieldMask types.
Other Message Features: All messages conform to Hashable
, Equatable
,
and CustomDebugStringConvertible
.
Convenience Initializer: Messages that have fields gain an additional convenience intializer that has an argument for every field. The arguments are defaulted so you can specify only the ones you actually need to set.
Fields: Each field is compiled into a property on the struct. Field names
are converted from snake_case
conventions in the proto file to
lowerCamelCase
property names in the Swift file. If the result conflicts with
a reserved word, an underscore will be appended to the property name.
Optional Fields: Optional fields generate Swift Optional properties. Such
properties have a default value of nil
unless overridden in the .proto file.
You can assign nil
to any such property to reset it to the default. For
optional fields without a default, you can test whether the field is nil
to
see if it has been set. There is currently no way to test if optional fields
that do have defaults have been set.
When serializing an optional field, the field is serialized if it has been set to a non-nil value. In particular, fields with defaults are not serialized if they have been reset by writing 'nil' but are serialized if you explicitly set them to the default value.
Required Fields: Required fields generate non-Optional properties. All such properties return suitable default values when read. If no default value is specified in the .proto, numeric fields default to zero, boolean fields default to false, string fields default to the empty string, byte fields default to the empty array, enum fields default to the appropriate default enum value, and message fields default to an empty object of that type. Currently, required fields are always serialized, even if they have not been changed from their default. This may change.
Repeated Fields: Repeated fields generate array-valued properties. All such properties default to an empty array.
Map: Map fields generate Dictionary-valued properties. When read, these properties default to an empty dictionary. The dictionary values can be mutated directly.
Proto3 Singular Fields: Proto3 does not support required or optional fields. Singular proto3 fields generate non-optional Swift properties. These fields are initialized to the appropriate Proto3 default value (zero for numeric fields, false for booleans, etc.) They are serialized if they have a non-default value.
Proto3 Singular message Fields: Proto3 singular message fields behave externally as other singular fields. In particular, reading such a field returns a valid empty message object by default. Internally, however, singular message fields are in fact stored as Swift optionals, but this is only done as an optimization and is not visible to clients.
Enums: Each enum in the proto file generates a Swift enum that implements
RawRepresentable with a base type of Int. The enum is nested within an enclosing
message, if any. The enum contains the specified cases from the source .proto
plus an extra UNRECOGNIZED(Int)
case that is used to carry unknown enum values
when decoding binary protobuf format. Enums with duplicate cases (more than one
case with the same integer value) are fully supported.
Oneof: Each oneof field generates an enum with a case for each field in the
oneof block. The containing message provides an accessor for the oneof field
itself that allows you to set or get the enum value directly and allows you to
use a switch
or if case
statement to conditionally handle the contents. The
containing message also provides shortcut accessors for each field in the oneof
that will return the corresponding value if present or nil
if that value is
not present.
Reflection: The standard Swift Mirror() facility can be used to inspect the fields on generated messages. Fields appear in the reflection if they would be serialized. In particular, proto2 required fields are always included, proto2 optional fields are included if they are non-nil, and proto3 singular fields are included if they do not have a default value.
Extensions: Each extension in a proto file generates two distinct components:
- An extension object that defines the type, field number, and other properties of the extension. This is defined at a scope corresponding to where the proto extension was defined.
- A Swift extension of the message struct that provides natural property access for the extension value on that message struct.
Each generated Swift file that defines extensions also has a static constant holding a ProtobufExtensionSet with all extensions declared in that file.
To decode a message with extensions, you need to first obtain or construct a ProtobufExtensionSet holding extension objects for all of the extensions you want to support. A single ProtobufExtensionSet can hold any number of extensions for any number of messages. You then provide this set to the deserializing initializer which will use it to identify and deserialize extension fields.
You need do nothing special to have extension values properly serialized.
To set or read extension properties, you simply use the standard property access.
Caveat: Extensions are not available in proto3.
Any: The Any message type is included in the runtime package as
Google_Protobuf_Any
. You can construct a message from a Google_Protobuf_Any
value via a convenience initializer available on any ProtobufMessageType:
init?(any: Google_Protobuf_Any)
. You can similarly construct a
Google_Protobuf_Any
object from any message using
Google_Protobuf_Any(message: ProtobufMessageType)
. To support this, each
generated message includes a property anyTypeURL
containing the URL for that
message type. This URL is included in the Any object when one is constructed
from a message, and is checked when constructing a message from an Any.
Caveat: Although Any fields can be encoded in both binary protobuf and JSON,
Google's spec places limits on translations between these two codings. As a
result, you should be careful with Any fields if you expect to use both JSON and
protobuf encodings.
Well-known types: Google has defined a number of "well-known types" as part of proto3. These are predefined messages that support common idioms. These well-known types are precompiled and bundled into the Swift runtime:
Proto Type | Swift Type |
---|---|
google.protobuf.Any | Google_Protobuf_Any |
google.protobuf.Api | Google_Protobuf_Api |
google.protobuf.BoolValue | Google_Protobuf_BoolValue |
google.protobuf.BytesValue | Google_Protobuf_BytesValue |
google.protobuf.DoubleValue | Google_Protobuf_DoubleValue |
google.protobuf.Duration | Google_Protobuf_Duration |
google.protobuf.Empty | Google_Protobuf_Empty |
google.protobuf.FieldMask | Google_Protobuf_FieldMask |
google.protobuf.FloatValue | Google_Protobuf_FloatValue |
google.protobuf.Int64Value | Google_Protobuf_Int64Value |
google.protobuf.ListValue | Google_Protobuf_ListValue |
google.protobuf.StringValue | Google_Protobuf_StringValue |
google.protobuf.Struct | Google_Protobuf_Struct |
google.protobuf.Timestamp | Google_Protobuf_Timestamp |
google.protobuf.Type | Google_Protobuf_Type |
google.protobuf.UInt32Value | Google_Protobuf_UInt32Value |
google.protobuf.UInt64Value | Google_Protobuf_UInt64Value |
google.protobuf.Value | Google_Protobuf_Value |
To use the well-known types in your own protos, you will need to have the
corresponding protos available so you can import
them into your proto file.
However, the compiled forms of these types are already available in the library;
you do not need to compile them or do anything to use them other than
import Protobuf
.
The terms proto2 and proto3 refer to two different dialects of the proto language. The older proto2 language dates back to 2008, the proto3 language was introduced in 2015. These should not be confused with versions of the protobuf project or the protoc program. In particular, the protoc 3.0 program has solid support for both proto2 and proto3 language dialects. Many people continue to use the proto2 language with protoc 3.0 because they have existing systems that depend on particular features of the proto2 language that were changed in proto3.