Merge pull request #2197 from thomasvl/swift_docs
Drop the swift docs directory (and content).pull/2198/head
commit
3d598eecee
1 changed files with 0 additions and 674 deletions
@ -1,674 +0,0 @@ |
|||||||
# Protocol Buffers in Swift |
|
||||||
|
|
||||||
## Objective |
|
||||||
|
|
||||||
This document describes the user-facing API and internal implementation of |
|
||||||
proto2 and proto3 messages in Apple’s Swift programming language. |
|
||||||
|
|
||||||
One of the key goals of protobufs is to provide idiomatic APIs for each |
|
||||||
language. In that vein, **interoperability with Objective-C is a non-goal of |
|
||||||
this proposal.** Protobuf users who need to pass messages between Objective-C |
|
||||||
and Swift code in the same application should use the existing Objective-C proto |
|
||||||
library. The goal of the effort described here is to provide an API for protobuf |
|
||||||
messages that uses features specific to Swift—optional types, algebraic |
|
||||||
enumerated types, value types, and so forth—in a natural way that will delight, |
|
||||||
rather than surprise, users of the language. |
|
||||||
|
|
||||||
## Naming |
|
||||||
|
|
||||||
* By convention, both typical protobuf message names and Swift structs/classes |
|
||||||
are `UpperCamelCase`, so for most messages, the name of a message can be the |
|
||||||
same as the name of its generated type. (However, see the discussion below |
|
||||||
about prefixes under [Packages](#packages).) |
|
||||||
|
|
||||||
* Enum cases in protobufs typically are `UPPERCASE_WITH_UNDERSCORES`, whereas |
|
||||||
in Swift they are `lowerCamelCase` (as of the Swift 3 API design |
|
||||||
guidelines). We will transform the names to match Swift convention, using |
|
||||||
a whitelist similar to the Objective-C compiler plugin to handle commonly |
|
||||||
used acronyms. |
|
||||||
|
|
||||||
* Typical fields in proto messages are `lowercase_with_underscores`, while in |
|
||||||
Swift they are `lowerCamelCase`. We will transform the names to match |
|
||||||
Swift convention by removing the underscores and uppercasing the subsequent |
|
||||||
letter. |
|
||||||
|
|
||||||
## Swift reserved words |
|
||||||
|
|
||||||
Swift has a large set of reserved words—some always reserved and some |
|
||||||
contextually reserved (that is, they can be used as identifiers in contexts |
|
||||||
where they would not be confused). As of Swift 2.2, the set of always-reserved |
|
||||||
words is: |
|
||||||
|
|
||||||
``` |
|
||||||
_, #available, #column, #else, #elseif, #endif, #file, #function, #if, #line, |
|
||||||
#selector, as, associatedtype, break, case, catch, class, continue, default, |
|
||||||
defer, deinit, do, dynamicType, else, enum, extension, fallthrough, false, for, |
|
||||||
func, guard, if, import, in, init, inout, internal, is, let, nil, operator, |
|
||||||
private, protocol, public, repeat, rethrows, return, self, Self, static, |
|
||||||
struct, subscript, super, switch, throw, throws, true, try, typealias, var, |
|
||||||
where, while |
|
||||||
``` |
|
||||||
|
|
||||||
The set of contextually reserved words is: |
|
||||||
|
|
||||||
``` |
|
||||||
associativity, convenience, dynamic, didSet, final, get, infix, indirect, |
|
||||||
lazy, left, mutating, none, nonmutating, optional, override, postfix, |
|
||||||
precedence, prefix, Protocol, required, right, set, Type, unowned, weak, |
|
||||||
willSet |
|
||||||
``` |
|
||||||
|
|
||||||
It is possible to use any reserved word as an identifier by escaping it with |
|
||||||
backticks (for example, ``let `class` = 5``). Other name-mangling schemes would |
|
||||||
require us to transform the names themselves (for example, by appending an |
|
||||||
underscore), which requires us to then ensure that the new name does not collide |
|
||||||
with something else in the same namespace. |
|
||||||
|
|
||||||
While the backtick feature may not be widely known by all Swift developers, a |
|
||||||
small amount of user education can address this and it seems like the best |
|
||||||
approach. We can unconditionally surround all property names with backticks to |
|
||||||
simplify generation. |
|
||||||
|
|
||||||
Some remapping will still be required, though, to avoid collisions between |
|
||||||
generated properties and the names of methods and properties defined in the base |
|
||||||
protocol/implementation of messages. |
|
||||||
|
|
||||||
# Features of Protocol Buffers |
|
||||||
|
|
||||||
This section describes how the features of the protocol buffer syntaxes (proto2 |
|
||||||
and proto3) map to features in Swift—what the code generated from a proto will |
|
||||||
look like, and how it will be implemented in the underlying library. |
|
||||||
|
|
||||||
## Packages |
|
||||||
|
|
||||||
Modules are the main form of namespacing in Swift, but they are not declared |
|
||||||
using syntactic constructs like namespaces in C++ or packages in Java. Instead, |
|
||||||
they are tied to build targets in Xcode (or, in the future with open-source |
|
||||||
Swift, declarations in a Swift Package Manager manifest). They also do not |
|
||||||
easily support nesting submodules (Clang module maps support this, but pure |
|
||||||
Swift does not yet provide a way to define submodules). |
|
||||||
|
|
||||||
We will generate types with fully-qualified underscore-delimited names. For |
|
||||||
example, a message `Baz` in package `foo.bar` would generate a struct named |
|
||||||
`Foo_Bar_Baz`. For each fully-qualified proto message, there will be exactly one |
|
||||||
unique type symbol emitted in the generated binary. |
|
||||||
|
|
||||||
Users are likely to balk at the ugliness of underscore-delimited names for every |
|
||||||
generated type. To improve upon this situation, we will add a new string file |
|
||||||
level option, `swift_package_typealias`, that can be added to `.proto` files. |
|
||||||
When present, this will cause `typealias`es to be added to the generated Swift |
|
||||||
messages that replace the package name prefix with the provided string. For |
|
||||||
example, the following `.proto` file: |
|
||||||
|
|
||||||
```protobuf |
|
||||||
option swift_package_typealias = "FBP"; |
|
||||||
package foo.bar; |
|
||||||
|
|
||||||
message Baz { |
|
||||||
// Message fields |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
would generate the following Swift source: |
|
||||||
|
|
||||||
```swift |
|
||||||
public struct Foo_Bar_Baz { |
|
||||||
// Message fields and other methods |
|
||||||
} |
|
||||||
|
|
||||||
typealias FBPBaz = Foo_Bar_Baz |
|
||||||
``` |
|
||||||
|
|
||||||
It should be noted that this type alias is recorded in the generated |
|
||||||
`.swiftmodule` so that code importing the module can refer to it, but it does |
|
||||||
not cause a new symbol to be generated in the compiled binary (i.e., we do not |
|
||||||
risk compiled size bloat by adding `typealias`es for every type). |
|
||||||
|
|
||||||
Other strategies to handle packages that were considered and rejected can be |
|
||||||
found in [Appendix A](#appendix-a-rejected-strategies-to-handle-packages). |
|
||||||
|
|
||||||
## Messages |
|
||||||
|
|
||||||
Proto messages are natural value types and we will generate messages as structs |
|
||||||
instead of classes. Users will benefit from Swift’s built-in behavior with |
|
||||||
regard to mutability. We will define a `ProtoMessage` protocol that defines the |
|
||||||
common methods and properties for all messages (such as serialization) and also |
|
||||||
lets users treat messages polymorphically. Any shared method implementations |
|
||||||
that do not differ between individual messages can be implemented in a protocol |
|
||||||
extension. |
|
||||||
|
|
||||||
The backing storage itself for fields of a message will be managed by a |
|
||||||
`ProtoFieldStorage` type that uses an internal dictionary keyed by field number, |
|
||||||
and whose values are the value of the field with that number (up-cast to Swift’s |
|
||||||
`Any` type). This class will provide type-safe getters and setters so that |
|
||||||
generated messages can manipulate this storage, and core serialization logic |
|
||||||
will live here as well. Furthermore, factoring the storage out into a separate |
|
||||||
type, rather than inlining the fields as stored properties in the message |
|
||||||
itself, lets us implement copy-on-write efficiently to support passing around |
|
||||||
large messages. (Furthermore, because the messages themselves are value types, |
|
||||||
inlining fields is not possible if the fields are submessages of the same type, |
|
||||||
or a type that eventually includes a submessage of the same type.) |
|
||||||
|
|
||||||
### Required fields (proto2 only) |
|
||||||
|
|
||||||
Required fields in proto2 messages seem like they could be naturally represented |
|
||||||
by non-optional properties in Swift, but this presents some problems/concerns. |
|
||||||
|
|
||||||
Serialization APIs permit partial serialization, which allows required fields to |
|
||||||
remain unset. Furthermore, other language APIs still provide `has*` and `clear*` |
|
||||||
methods for required fields, and knowing whether a property has a value when the |
|
||||||
message is in memory is still useful. |
|
||||||
|
|
||||||
For example, an e-mail draft message may have the “to” address required on the |
|
||||||
wire, but when the user constructs it in memory, it doesn’t make sense to force |
|
||||||
a value until they provide one. We only want to force a value to be present when |
|
||||||
the message is serialized to the wire. Using non-optional properties prevents |
|
||||||
this use case, and makes client usage awkward because the user would be forced |
|
||||||
to select a sentinel or placeholder value for any required fields at the time |
|
||||||
the message was created. |
|
||||||
|
|
||||||
### Default values |
|
||||||
|
|
||||||
In proto2, fields can have a default value specified that may be a value other |
|
||||||
than the default value for its corresponding language type (for example, a |
|
||||||
default value of 5 instead of 0 for an integer). When reading a field that is |
|
||||||
not explicitly set, the user expects to get that value. This makes Swift |
|
||||||
optionals (i.e., `Foo?`) unsuitable for fields in general. Unfortunately, we |
|
||||||
cannot implement our own “enhanced optional” type without severely complicating |
|
||||||
usage (Swift’s use of type inference and its lack of implicit conversions would |
|
||||||
require manual unwrapping of every property value). |
|
||||||
|
|
||||||
Instead, we can use **implicitly unwrapped optionals.** For example, a property |
|
||||||
generated for a field of type `int32` would have Swift type `Int32!`. These |
|
||||||
properties would behave with the following characteristics, which mirror the |
|
||||||
nil-resettable properties used elsewhere in Apple’s SDKs (for example, |
|
||||||
`UIView.tintColor`): |
|
||||||
|
|
||||||
* Assigning a non-nil value to a property sets the field to that value. |
|
||||||
* Assigning nil to a property clears the field (its internal representation is |
|
||||||
nilled out). |
|
||||||
* Reading the value of a property returns its value if it is set, or returns |
|
||||||
its default value if it is not set. Reading a property never returns nil. |
|
||||||
|
|
||||||
The final point in the list above implies that the optional cannot be checked to |
|
||||||
determine if the field is set to a value other than its default: it will never |
|
||||||
be nil. Instead, we must provide `has*` methods for each field to allow the user |
|
||||||
to check this. These methods will be public in proto2. In proto3, these methods |
|
||||||
will be private (if generated at all), since the user can test the returned |
|
||||||
value against the zero value for that type. |
|
||||||
|
|
||||||
### Autocreation of nested messages |
|
||||||
|
|
||||||
For convenience, dotting into an unset field representing a nested message will |
|
||||||
return an instance of that message with default values. As in the Objective-C |
|
||||||
implementation, this does not actually cause the field to be set until the |
|
||||||
returned message is mutated. Fortunately, thanks to the way mutability of value |
|
||||||
types is implemented in Swift, the language automatically handles the |
|
||||||
reassignment-on-mutation for us. A static singleton instance containing default |
|
||||||
values can be associated with each message that can be returned when reading, so |
|
||||||
copies are only made by the Swift runtime when mutation occurs. For example, |
|
||||||
given the following proto: |
|
||||||
|
|
||||||
```protobuf |
|
||||||
message Node { |
|
||||||
Node child = 1; |
|
||||||
string value = 2 [default = "foo"]; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
The following Swift code would act as commented, where setting deeply nested |
|
||||||
properties causes the copies and mutations to occur as the assignment statement |
|
||||||
is unwound: |
|
||||||
|
|
||||||
```swift |
|
||||||
var node = Node() |
|
||||||
|
|
||||||
let s = node.child.child.value |
|
||||||
// 1. node.child returns the "default Node". |
|
||||||
// 2. Reading .child on the result of (1) returns the same default Node. |
|
||||||
// 3. Reading .value on the result of (2) returns the default value "foo". |
|
||||||
|
|
||||||
node.child.child.value = "bar" |
|
||||||
// 4. Setting .value on the default Node causes a copy to be made and sets |
|
||||||
// the property on that copy. Subsequently, the language updates the |
|
||||||
// value of "node.child.child" to point to that copy. |
|
||||||
// 5. Updating "node.child.child" in (4) requires another copy, because |
|
||||||
// "node.child" was also the instance of the default node. The copy is |
|
||||||
// assigned back to "node.child". |
|
||||||
// 6. Setting "node.child" in (5) is a simple value reassignment, since |
|
||||||
// "node" is a mutable var. |
|
||||||
``` |
|
||||||
|
|
||||||
In other words, the generated messages do not internally have to manage parental |
|
||||||
relationships to backfill the appropriate properties on mutation. Swift provides |
|
||||||
this for free. |
|
||||||
|
|
||||||
## Scalar value fields |
|
||||||
|
|
||||||
Proto scalar value fields will map to Swift types in the following way: |
|
||||||
|
|
||||||
.proto Type | Swift Type |
|
||||||
----------- | ------------------- |
|
||||||
`double` | `Double` |
|
||||||
`float` | `Float` |
|
||||||
`int32` | `Int32` |
|
||||||
`int64` | `Int64` |
|
||||||
`uint32` | `UInt32` |
|
||||||
`uint64` | `UInt64` |
|
||||||
`sint32` | `Int32` |
|
||||||
`sint64` | `Int64` |
|
||||||
`fixed32` | `UInt32` |
|
||||||
`fixed64` | `UInt64` |
|
||||||
`sfixed32` | `Int32` |
|
||||||
`sfixed64` | `Int64` |
|
||||||
`bool` | `Bool` |
|
||||||
`string` | `String` |
|
||||||
`bytes` | `Foundation.NSData` |
|
||||||
|
|
||||||
The proto spec defines a number of integral types that map to the same Swift |
|
||||||
type; for example, `intXX`, `sintXX`, and `sfixedXX` are all signed integers, |
|
||||||
and `uintXX` and `fixedXX` are both unsigned integers. No other language |
|
||||||
implementation distinguishes these further, so we do not do so either. The |
|
||||||
rationale is that the various types only serve to distinguish how the value is |
|
||||||
**encoded on the wire**; once loaded in memory, the user is not concerned about |
|
||||||
these variations. |
|
||||||
|
|
||||||
Swift’s lack of implicit conversions among types will make it slightly annoying |
|
||||||
to use these types in a context expecting an `Int`, or vice-versa, but since |
|
||||||
this is a data-interchange format with explicitly-sized fields, we should not |
|
||||||
hide that information from the user. Users will have to explicitly write |
|
||||||
`Int(message.myField)`, for example. |
|
||||||
|
|
||||||
## Embedded message fields |
|
||||||
|
|
||||||
Embedded message fields can be represented using an optional variable of the |
|
||||||
generated message type. Thus, the message |
|
||||||
|
|
||||||
```protobuf |
|
||||||
message Foo { |
|
||||||
Bar bar = 1; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
would be represented in Swift as |
|
||||||
|
|
||||||
```swift |
|
||||||
public struct Foo: ProtoMessage { |
|
||||||
public var bar: Bar! { |
|
||||||
get { ... } |
|
||||||
set { ... } |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
If the user explicitly sets `bar` to nil, or if it was never set when read from |
|
||||||
the wire, retrieving the value of `bar` would return a default, statically |
|
||||||
allocated instance of `Bar` containing default values for its fields. This |
|
||||||
achieves the desired behavior for default values in the same way that scalar |
|
||||||
fields are designed, and also allows users to deep-drill into complex object |
|
||||||
graphs to get or set fields without checking for nil at each step. |
|
||||||
|
|
||||||
## Enum fields |
|
||||||
|
|
||||||
The design and implementation of enum fields will differ somewhat drastically |
|
||||||
depending on whether the message being generated is a proto2 or proto3 message. |
|
||||||
|
|
||||||
### proto2 enums |
|
||||||
|
|
||||||
For proto2, we do not need to be concerned about unknown enum values, so we can |
|
||||||
use the simple raw-value enum syntax provided by Swift. So the following enum in |
|
||||||
proto2: |
|
||||||
|
|
||||||
```protobuf |
|
||||||
enum ContentType { |
|
||||||
TEXT = 0; |
|
||||||
IMAGE = 1; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
would become this Swift enum: |
|
||||||
|
|
||||||
```swift |
|
||||||
public enum ContentType: Int32, NilLiteralConvertible { |
|
||||||
case text = 0 |
|
||||||
case image = 1 |
|
||||||
|
|
||||||
public init(nilLiteral: ()) { |
|
||||||
self = .text |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
See below for the discussion about `NilLiteralConvertible`. |
|
||||||
|
|
||||||
### proto3 enums |
|
||||||
|
|
||||||
For proto3, we need to be able to preserve unknown enum values that may come |
|
||||||
across the wire so that they can be written back if unmodified. We can |
|
||||||
accomplish this in Swift by using a case with an associated value for unknowns. |
|
||||||
So the following enum in proto3: |
|
||||||
|
|
||||||
```protobuf |
|
||||||
enum ContentType { |
|
||||||
TEXT = 0; |
|
||||||
IMAGE = 1; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
would become this Swift enum: |
|
||||||
|
|
||||||
```swift |
|
||||||
public enum ContentType: RawRepresentable, NilLiteralConvertible { |
|
||||||
case text |
|
||||||
case image |
|
||||||
case UNKNOWN_VALUE(Int32) |
|
||||||
|
|
||||||
public typealias RawValue = Int32 |
|
||||||
|
|
||||||
public init(nilLiteral: ()) { |
|
||||||
self = .text |
|
||||||
} |
|
||||||
|
|
||||||
public init(rawValue: RawValue) { |
|
||||||
switch rawValue { |
|
||||||
case 0: self = .text |
|
||||||
case 1: self = .image |
|
||||||
default: self = .UNKNOWN_VALUE(rawValue) |
|
||||||
} |
|
||||||
|
|
||||||
public var rawValue: RawValue { |
|
||||||
switch self { |
|
||||||
case .text: return 0 |
|
||||||
case .image: return 1 |
|
||||||
case .UNKNOWN_VALUE(let value): return value |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Note that the use of a parameterized case prevents us from inheriting from the |
|
||||||
raw `Int32` type; Swift does not allow an enum with a raw type to have cases |
|
||||||
with arguments. Instead, we must implement the raw value initializer and |
|
||||||
computed property manually. The `UNKNOWN_VALUE` case is explicitly chosen to be |
|
||||||
"ugly" so that it stands out and does not conflict with other possible case |
|
||||||
names. |
|
||||||
|
|
||||||
Using this approach, proto3 consumers must always have a default case or handle |
|
||||||
the `.UNKNOWN_VALUE` case to satisfy case exhaustion in a switch statement; the |
|
||||||
Swift compiler considers it an error if switch statements are not exhaustive. |
|
||||||
|
|
||||||
### NilLiteralConvertible conformance |
|
||||||
|
|
||||||
This is required to clean up the usage of enum-typed properties in switch |
|
||||||
statements. Unlike other field types, enum properties cannot be |
|
||||||
implicitly-unwrapped optionals without requiring that uses in switch statements |
|
||||||
be explicitly unwrapped. For example, if we consider a message with the enum |
|
||||||
above, this usage will fail to compile: |
|
||||||
|
|
||||||
```swift |
|
||||||
// Without NilLiteralConvertible conformance on ContentType |
|
||||||
public struct SomeMessage: ProtoMessage { |
|
||||||
public var contentType: ContentType! { ... } |
|
||||||
} |
|
||||||
|
|
||||||
// ERROR: no case named text or image |
|
||||||
switch someMessage.contentType { |
|
||||||
case .text: { ... } |
|
||||||
case .image: { ... } |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Even though our implementation guarantees that `contentType` will never be nil, |
|
||||||
if it is an optional type, its cases would be `some` and `none`, not the cases |
|
||||||
of the underlying enum type. In order to use it in this context, the user must |
|
||||||
write `someMessage.contentType!` in their switch statement. |
|
||||||
|
|
||||||
Making the enum itself `NilLiteralConvertible` permits us to make the property |
|
||||||
non-optional, so the user can still set it to nil to clear it (i.e., reset it to |
|
||||||
its default value), while eliminating the need to explicitly unwrap it in a |
|
||||||
switch statement. |
|
||||||
|
|
||||||
```swift |
|
||||||
// With NilLiteralConvertible conformance on ContentType |
|
||||||
public struct SomeMessage: ProtoMessage { |
|
||||||
// Note that the property type is no longer optional |
|
||||||
public var contentType: ContentType { ... } |
|
||||||
} |
|
||||||
|
|
||||||
// OK: Compiles and runs as expected |
|
||||||
switch someMessage.contentType { |
|
||||||
case .text: { ... } |
|
||||||
case .image: { ... } |
|
||||||
} |
|
||||||
|
|
||||||
// The enum can be reset to its default value this way |
|
||||||
someMessage.contentType = nil |
|
||||||
``` |
|
||||||
|
|
||||||
One minor oddity with this approach is that nil will be auto-converted to the |
|
||||||
default value of the enum in any context, not just field assignment. In other |
|
||||||
words, this is valid: |
|
||||||
|
|
||||||
```swift |
|
||||||
func foo(contentType: ContentType) { ... } |
|
||||||
foo(nil) // Inside foo, contentType == .text |
|
||||||
``` |
|
||||||
|
|
||||||
That being said, the advantage of being able to simultaneously support |
|
||||||
nil-resettability and switch-without-unwrapping outweighs this side effect, |
|
||||||
especially if appropriately documented. It is our hope that a new form of |
|
||||||
resettable properties will be added to Swift that eliminates this inconsistency. |
|
||||||
Some community members have already drafted or sent proposals for review that |
|
||||||
would benefit our designs: |
|
||||||
|
|
||||||
* [SE-0030: Property Behaviors] |
|
||||||
(https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md) |
|
||||||
* [Drafted: Resettable Properties] |
|
||||||
(https://github.com/patters/swift-evolution/blob/master/proposals/0000-resettable-properties.md) |
|
||||||
|
|
||||||
### Enum aliases |
|
||||||
|
|
||||||
The `allow_alias` option in protobuf slightly complicates the use of Swift enums |
|
||||||
to represent that type, because raw values of cases in an enum must be unique. |
|
||||||
Swift lets us define static variables in an enum that alias actual cases. For |
|
||||||
example, the following protobuf enum: |
|
||||||
|
|
||||||
```protobuf |
|
||||||
enum Foo { |
|
||||||
option allow_alias = true; |
|
||||||
BAR = 0; |
|
||||||
BAZ = 0; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
will be represented in Swift as: |
|
||||||
|
|
||||||
```swift |
|
||||||
public enum Foo: Int32, NilLiteralConvertible { |
|
||||||
case bar = 0 |
|
||||||
static public let baz = bar |
|
||||||
|
|
||||||
// ... etc. |
|
||||||
} |
|
||||||
|
|
||||||
// Can still use .baz shorthand to reference the alias in contexts |
|
||||||
// where the type is inferred |
|
||||||
``` |
|
||||||
|
|
||||||
That is, we use the first name as the actual case and use static variables for |
|
||||||
the other aliases. One drawback to this approach is that the static aliases |
|
||||||
cannot be used as cases in a switch statement (the compiler emits the error |
|
||||||
*“Enum case ‘baz’ not found in type ‘Foo’”*). However, in our own code bases, |
|
||||||
there are only a few places where enum aliases are not mere renamings of an |
|
||||||
older value, but they also don’t appear to be the type of value that one would |
|
||||||
expect to switch on (for example, a group of named constants representing |
|
||||||
metrics rather than a set of options), so this restriction is not significant. |
|
||||||
|
|
||||||
This strategy also implies that changing the name of an enum and adding the old |
|
||||||
name as an alias below the new name will be a breaking change in the generated |
|
||||||
Swift code. |
|
||||||
|
|
||||||
## Oneof types |
|
||||||
|
|
||||||
The `oneof` feature represents a “variant/union” data type that maps nicely to |
|
||||||
Swift enums with associated values (algebraic types). These fields can also be |
|
||||||
accessed independently though, and, specifically in the case of proto2, it’s |
|
||||||
reasonable to expect access to default values when accessing a field that is not |
|
||||||
explicitly set. |
|
||||||
|
|
||||||
Taking all this into account, we can represent a `oneof` in Swift with two sets |
|
||||||
of constructs: |
|
||||||
|
|
||||||
* Properties in the message that correspond to the `oneof` fields. |
|
||||||
* A nested enum named after the `oneof` and which provides the corresponding |
|
||||||
field values as case arguments. |
|
||||||
|
|
||||||
This approach fulfills the needs of proto consumers by providing a |
|
||||||
Swift-idiomatic way of simultaneously checking which field is set and accessing |
|
||||||
its value, providing individual properties to access the default values |
|
||||||
(important for proto2), and safely allows a field to be moved into a `oneof` |
|
||||||
without breaking clients. |
|
||||||
|
|
||||||
Consider the following proto: |
|
||||||
|
|
||||||
```protobuf |
|
||||||
message MyMessage { |
|
||||||
oneof record { |
|
||||||
string name = 1 [default = "unnamed"]; |
|
||||||
int32 id_number = 2 [default = 0]; |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
In Swift, we would generate an enum, a property for that enum, and properties |
|
||||||
for the fields themselves: |
|
||||||
|
|
||||||
```swift |
|
||||||
public struct MyMessage: ProtoMessage { |
|
||||||
public enum Record: NilLiteralConvertible { |
|
||||||
case name(String) |
|
||||||
case idNumber(Int32) |
|
||||||
case NOT_SET |
|
||||||
|
|
||||||
public init(nilLiteral: ()) { self = .NOT_SET } |
|
||||||
} |
|
||||||
|
|
||||||
// This is the "Swifty" way of accessing the value |
|
||||||
public var record: Record { ... } |
|
||||||
|
|
||||||
// Direct access to the underlying fields |
|
||||||
public var name: String! { ... } |
|
||||||
public var idNumber: Int32! { ... } |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
This makes both usage patterns possible: |
|
||||||
|
|
||||||
```swift |
|
||||||
// Usage 1: Case-based dispatch |
|
||||||
switch message.record { |
|
||||||
case .name(let name): |
|
||||||
// Do something with name if it was explicitly set |
|
||||||
case .idNumber(let id): |
|
||||||
// Do something with id_number if it was explicitly set |
|
||||||
case .NOT_SET: |
|
||||||
// Do something if it’s not set |
|
||||||
} |
|
||||||
|
|
||||||
// Usage 2: Direct access for default value fallback |
|
||||||
// Sets the label text to the name if it was explicitly set, or to |
|
||||||
// "unnamed" (the default value for the field) if id_number was set |
|
||||||
// instead |
|
||||||
let myLabel = UILabel() |
|
||||||
myLabel.text = message.name |
|
||||||
``` |
|
||||||
|
|
||||||
As with proto enums, the generated `oneof` enum conforms to |
|
||||||
`NilLiteralConvertible` to avoid switch statement issues. Setting the property |
|
||||||
to nil will clear it (i.e., reset it to `NOT_SET`). |
|
||||||
|
|
||||||
## Unknown Fields (proto2 only) |
|
||||||
|
|
||||||
To be written. |
|
||||||
|
|
||||||
## Extensions (proto2 only) |
|
||||||
|
|
||||||
To be written. |
|
||||||
|
|
||||||
## Reflection and Descriptors |
|
||||||
|
|
||||||
We will not include reflection or descriptors in the first version of the Swift |
|
||||||
library. The use cases for reflection on mobile are not as strong and the static |
|
||||||
data to represent the descriptors would add bloat when we wish to keep the code |
|
||||||
size small. |
|
||||||
|
|
||||||
In the future, we will investigate whether they can be included as extensions |
|
||||||
which might be able to be excluded from a build and/or automatically dead |
|
||||||
stripped by the compiler if they are not used. |
|
||||||
|
|
||||||
## Appendix A: Rejected strategies to handle packages |
|
||||||
|
|
||||||
### Each package is its own Swift module |
|
||||||
|
|
||||||
Each proto package could be declared as its own Swift module, replacing dots |
|
||||||
with underscores (e.g., package `foo.bar` becomes module `Foo_Bar`). Then, users |
|
||||||
would simply import modules containing whatever proto modules they want to use |
|
||||||
and refer to the generated types by their short names. |
|
||||||
|
|
||||||
**This solution is simply not possible, however.** Swift modules cannot |
|
||||||
circularly reference each other, but there is no restriction against proto |
|
||||||
packages doing so. Circular imports are forbidden (e.g., `foo.proto` importing |
|
||||||
`bar.proto` importing `foo.proto`), but nothing prevents package `foo` from |
|
||||||
using a type in package `bar` which uses a different type in package `foo`, as |
|
||||||
long as there is no import cycle. If these packages were generated as Swift |
|
||||||
modules, then `Foo` would contain an `import Bar` statement and `Bar` would |
|
||||||
contain an `import Foo` statement, and there is no way to compile this. |
|
||||||
|
|
||||||
### Ad hoc namespacing with structs |
|
||||||
|
|
||||||
We can “fake” namespaces in Swift by declaring empty structs with private |
|
||||||
initializers. Since modules are constructed based on compiler arguments, not by |
|
||||||
syntactic constructs, and because there is no pure Swift way to define |
|
||||||
submodules (even though Clang module maps support this), there is no |
|
||||||
source-drive way to group generated code into namespaces aside from this |
|
||||||
approach. |
|
||||||
|
|
||||||
Types can be added to those intermediate package structs using Swift extensions. |
|
||||||
For example, a message `Baz` in package `foo.bar` could be represented in Swift |
|
||||||
as follows: |
|
||||||
|
|
||||||
```swift |
|
||||||
public struct Foo { |
|
||||||
private init() {} |
|
||||||
} |
|
||||||
|
|
||||||
public extension Foo { |
|
||||||
public struct Bar { |
|
||||||
private init() {} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public extension Foo.Bar { |
|
||||||
public struct Baz { |
|
||||||
// Message fields and other methods |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
let baz = Foo.Bar.Baz() |
|
||||||
``` |
|
||||||
|
|
||||||
Each of these constructs would actually be defined in a separate file; Swift |
|
||||||
lets us keep them separate and add multiple structs to a single “namespace” |
|
||||||
through extensions. |
|
||||||
|
|
||||||
Unfortunately, these intermediate structs generate symbols of their own |
|
||||||
(metatype information in the data segment). This becomes problematic if multiple |
|
||||||
build targets contain Swift sources generated from different messages in the |
|
||||||
same package. At link time, these symbols would collide, resulting in multiple |
|
||||||
definition errors. |
|
||||||
|
|
||||||
This approach also has the disadvantage that there is no automatic “short” way |
|
||||||
to refer to the generated messages at the deepest nesting levels; since this use |
|
||||||
of structs is a hack around the lack of namespaces, there is no equivalent to |
|
||||||
import (Java) or using (C++) to simplify this. Users would have to declare type |
|
||||||
aliases to make this cleaner, or we would have to generate them for users. |
|
Loading…
Reference in new issue