gRPC Swift provides first-class support for defining services in Protobuf and generating type-safe Swift client/server code, including unary and bidirectional streaming RPCs. New Xcode build plugin integration makes it trivial to add gRPC to iOS apps and Swift server targets alike.
⢠Eliminates hand-crafted networking code by generating type-safe clients from a .proto spec, reducing bugs and keeping client/server in sync automatically.
⢠First-class support for all four RPC types (unary, client-streaming, server-streaming, bidirectional) enables real-time features like live race telemetry or event feeds with native async/await and AsyncSequence.
⢠Binary Protobuf wire format is ~50% smaller than equivalent JSON, improving performance especially on poor mobile connections.
Demonstrates setting up a shared gRPC client via the SwiftUI environment and consuming a server-streaming RPC to display live race events using async/await and AsyncSequence.
import SwiftUI
import GRPCCore
import GRPCNIOTransportHTTP2
// MARK: - Client Manager (shared via Environment)
@MainActor
final class GRPCClientManager: ObservableObject {
private var grpcClient: GRPCClient<HTTP2ClientTransport.Posix>?
func client() async throws -> GRPCClient<HTTP2ClientTransport.Posix> {
if let existing = grpcClient { return existing }
let transport = try HTTP2ClientTransport.Posix(
target: .dns(host: "localhost", port: 9090),
config: .defaults(transportSecurity: .plaintext)
)
let client = GRPCClient(transport: transport)
self.grpcClient = client
Task { try await client.run() }
return client
}
func disconnect() {
grpcClient?.beginGracefulShutdown()
grpcClient = nil
}
}
// MARK: - Live Race View
struct LiveRaceView: View {
@EnvironmentObject var clientManager: GRPCClientManager
@State private var events: [String] = []
@Environment(\.scenePhase) private var scenePhase
var body: some View {
List(events, id: \.self) { event in
Text(event)
}
.navigationTitle("Live Race Feed")
.task {
await streamRaceEvents(raceName: "Finite Loops")
}
.onChange(of: scenePhase) { _, newPhase in
if newPhase == .background {
clientManager.disconnect()
}
}
}
private func streamRaceEvents(raceName: String) async {
do {
let grpcClient = try await clientManager.client()
// Generated client from proto build plugin
let swiftKartClient = SwiftKart_RaceService.Client(wrapping: grpcClient)
var request = SwiftKart_FollowRaceRequest()
request.raceName = raceName
request.subscriptions = [.kartLocations, .standings]
// Server-streaming RPC: single request, many responses
try await swiftKartClient.followRace(request) { responseStream in
for try await response in responseStream.messages {
switch response.event {
case .locations(let loc):
let summary = loc.kartPositions.map { "Kart \($0.kartID): (\($0.x), \($0.y))" }.joined(separator: ", ")
await MainActor.run { events.append("Positions: " + summary) }
case .standings(let s):
let top = s.entries.prefix(3).map { "\($0.position). \($0.driverName)" }.joined(separator: " | ")
await MainActor.run { events.append("Standings: " + top) }
case .none:
break
}
}
}
} catch {
await MainActor.run { events.append("Error: \(error.localizedDescription)") }
}
}
}
// MARK: - App Entry Point
@main
struct SwiftKartApp: App {
@StateObject private var clientManager = GRPCClientManager()
var body: some Scene {
WindowGroup {
NavigationStack {
LiveRaceView()
}
.environmentObject(clientManager)
}
}
}The GRPCProtobufGenerator build plugin must be explicitly trusted in Xcode on first use. Avoid creating a new gRPC client per view ā share a single client via the SwiftUI Environment to allow connection reuse. Disconnect the client when the app backgrounds to free resources. The JSON config file next to your .proto determines whether client, server, or both are generated.
No specific hardware constraints; server-side use requires Linux or macOS with Swift 6+.
More iOS 27 APIs land every week.
Get notified when new capabilities are published ā no noise, just signal.