blob: 34702f9fdd56ffebbd989adeb7a6bb802357d539 [file] [view]
---
title: Swift gRPC
sidebar_position: 14
id: swift
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---
Fory can generate Swift gRPC service companions for schemas that define
services. The companion provides the usual gRPC service providers, clients,
method descriptors, and service metadata, while request and response objects are
serialized with Fory instead of protobuf.
Use this mode when both RPC peers are generated from the same Fory IDL, protobuf
IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use
normal protobuf gRPC generation for APIs that must be consumed by generic
protobuf clients, reflection tools, or components that expect protobuf bytes.
The companion targets [grpc-swift](https://github.com/grpc/grpc-swift) 1.x. That
line keeps the same platform floor as the Fory Swift package (macOS 13, iOS 16);
grpc-swift 2.x requires a newer floor.
## Add Dependencies
The `Fory` package does not depend on grpc-swift. Add grpc-swift in the package
that compiles or runs the generated companions:
```swift
// Package.swift
dependencies: [
.package(url: "https://github.com/apache/fory.git", exact: "$version"),
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.23.0"),
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Fory", package: "fory"),
.product(name: "GRPC", package: "grpc-swift"),
]
)
]
```
## Define a Service
Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
`rpc_service` definitions. A Fory IDL service looks like this:
```protobuf
package demo.greeter;
message HelloRequest {
string name = 1;
}
message HelloReply {
string reply = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
```
Generate Swift model and gRPC companion code with `--grpc`:
```bash
foryc service.fdl --swift_out=./Sources/App --grpc
```
For this schema the Swift generator emits:
| File | Purpose |
| -------------------------------- | -------------------------------------------- |
| `demo/greeter/greeter.swift` | Fory model types and the `ForyModule` helper |
| `demo/greeter/GreeterGrpc.swift` | gRPC providers, client, and service metadata |
Generated gRPC symbols are prefixed with the package, so the schema above emits
`Demo_Greeter_GreeterAsyncProvider`, `Demo_Greeter_GreeterAsyncClient`, and
`Demo_Greeter_GreeterProvider`. A schema with no package drops the prefix
(`GreeterAsyncProvider`).
## Implement a Server
Conform a type to the generated `async`/`await` provider and host it with a
normal grpc-swift `Server`:
```swift
import Fory
import GRPC
import NIOPosix
final class GreeterService: Demo_Greeter_GreeterAsyncProvider {
func sayHello(
request: Demo.Greeter.HelloRequest,
context: GRPCAsyncServerCallContext
) async throws -> Demo.Greeter.HelloReply {
Demo.Greeter.HelloReply(reply: "Hello, " + request.name)
}
}
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let server = try await Server.insecure(group: group)
.withServiceProviders([GreeterService()])
.bind(host: "127.0.0.1", port: 1234)
.get()
```
Request and response types are registered by the generated schema module that
the companion uses, so server code does not register serializers by hand. An
`EventLoopFuture`-based `Demo_Greeter_GreeterProvider` is also emitted for
servers that do not use `async`/`await`.
## Create a Client
Use the generated async client over a grpc-swift channel:
```swift
import Fory
import GRPC
import NIOPosix
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let channel = try GRPCChannelPool.with(
target: .host("127.0.0.1", port: 1234),
transportSecurity: .plaintext,
eventLoopGroup: group)
let client = Demo_Greeter_GreeterAsyncClient(channel: channel)
let reply = try await client.sayHello(Demo.Greeter.HelloRequest(name: "Fory"))
print(reply.reply)
```
## Streaming RPCs
Fory service definitions can use the four gRPC streaming shapes:
```protobuf
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc BidiHello (stream HelloRequest) returns (stream HelloReply);
}
```
Streaming methods present clean request and response types. The provider receives
a response writer (`send(_:)`) for server output and an `AsyncSequence` for client
input; the client returns an `AsyncSequence` of responses for server-streamed
replies:
```swift
// Server side
func lotsOfReplies(
request: Demo.Greeter.HelloRequest,
responseStream: Demo_Greeter_GreeterAsyncResponseStream<Demo.Greeter.HelloReply>,
context: GRPCAsyncServerCallContext
) async throws {
try await responseStream.send(Demo.Greeter.HelloReply(reply: "Hi " + request.name))
}
// Client side
for try await reply in client.lotsOfReplies(Demo.Greeter.HelloRequest(name: "Fory")) {
print(reply.reply)
}
```
## gRPC Runtime Behavior
Generated companions carry Fory-encoded bytes inside a private `GRPCPayload`
wrapper. The Swift `Fory` instance is single-threaded, so the wrapper uses one
`Fory` per thread, built from the schema module's configuration and registrations,
which makes concurrent RPCs safe without sharing a single instance. Imported
request and response types resolve to their own namespace and are registered
transitively through the owning module, so a service that crosses an import
boundary works without extra registration.
## Swift Language Mode
Compile generated companions in Swift 5 language mode (use
`swift-tools-version:5.9`, or set `swiftLanguageMode(.v5)` on the target in a
6.x manifest). grpc-swift moves each request and response between the calling
task and the event loop, so the wire wrapper requires a `Sendable` payload, and
generated Fory Swift models do not declare that conformance. This applies to
every call shape, including unary calls, not only the streaming ones.
## Known Limitations
The generated client is async/await only. grpc-swift's `EventLoopFuture` client
returns call objects parameterized by the on-the-wire message type, which would
expose the internal Fory wrapper, so it is not emitted. Both providers (async and
`EventLoopFuture`) are generated.
Interceptors are not generated. grpc-swift interceptors are typed on the
on-the-wire message, which is the internal Fory wrapper; emitting interceptor
hooks would expose that wrapper. Use a custom channel or server configuration for
cross-cutting concerns instead.
RPC names must produce a usable Swift member. The compiler rejects an rpc whose
name is only underscores, because it normalizes to `_`, which Swift reserves for
discards. It also rejects `handle`, `serviceName`, `channel`, and
`defaultCallOptions`, which collide with members of the generated provider and
client. Rename the rpc in the schema.
Swift models put each package under a nested `enum` namespace, so two schemas that
share a top-level package component (for example `demo.shared` and `demo.greeter`)
both emit `public enum Demo`. The compiler rejects that with a top-level symbol
collision before it writes either file. This is a model-generation behavior, not
specific to gRPC, but it also affects a service that imports across such packages.
Give the schemas disjoint top-level packages (for example `shared.models` and
`greeter.api`). Generating into separate Swift modules with one `foryc` invocation
each only helps unrelated schemas, because the preflight collects imports
recursively: compiling `demo.greeter` still includes `demo.shared` in the graph and
rejects the duplicate `Demo` even if the shared schema was generated in another
invocation. An import graph needs disjoint top-level packages.
## Troubleshooting
### Missing grpc-swift Types
If the build cannot find `GRPCAsyncServerCallContext`, `Server`, or
`GRPCChannelPool`, add the grpc-swift dependency and the `GRPC` product to the
target that compiles the generated companion.
### Protobuf Clients Cannot Decode the Service
Generated companions exchange Fory-encoded bodies, not protobuf bytes. A generic
protobuf client cannot decode them. Both peers must be generated from the same
Fory IDL and use the generated Fory companions.