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
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 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.
The Fory package does not depend on grpc-swift. Add grpc-swift in the package that compiles or runs the generated companions:
// 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"), ] ) ]
Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers rpc_service definitions. A Fory IDL service looks like this:
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:
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).
Conform a type to the generated async/await provider and host it with a normal grpc-swift Server:
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.
Use the generated async client over a grpc-swift channel:
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)
Fory service definitions can use the four gRPC streaming shapes:
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:
// 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) }
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.
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.
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.
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.
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.