View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • AI & Machine Learning
    • App Intents
    • Apple Intelligence
    • Games
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vídeos

Abrir menu Fechar menu
  • Coleções
  • Todos os vídeos
  • Sobre

Mais vídeos

  • Sobre
  • Código
  • Desenvolva apps e serviços em tempo real com gRPC e Swift

    Crie experiências ao vivo envolventes com gRPC no seu app e back-end em Swift. gRPC é um framework de RPC de código aberto projetado para APIs de streaming bidirecional de alto desempenho. Confira como o pacote gRPC Swift oferece um tempo de execução moderno e seguro, desenvolvido com a concorrência do Swift. Saiba como as ferramentas integradas simplificam seu fluxo de trabalho e ajudam você a disponibilizar recursos em tempo real com facilidade.

    Capítulos

    • 0:00 - Introdução
    • 1:39 - Conheça o gRPC
    • 2:13 - Visão geral do app e configuração de demonstração
    • 3:30 - Definindo o RPC ListRaces
    • 4:30 - Configurando o Xcode para gerar código gRPC
    • 7:50 - Gerenciando o ciclo de vida do cliente gRPC
    • 9:36 - Formato de Mensagem Protobuf e Eficiência Binária
    • 12:33 - Implementando um RPC de Streaming Bidirecional
    • 20:11 - Implantando o serviço
    • 23:11 - Próximas etapas

    Recursos

    • About gRPC
    • gRPC Swift Extras
    • gRPC Swift Protobuf
    • gRPC Swift NIO Transport
    • gRPC Swift
    • Swift on Server
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC25

    • Conheça o framework Containerization

    WWDC24

    • Explore the Swift on Server ecosystem

    WWDC23

    • Meet Swift OpenAPI Generator
  • Buscar neste vídeo...
    • 3:38 - ListRaces RPC definition

      edition = "2024";
      
      import "google/protobuf/timestamp.proto";
      
      service SwiftKartService {
        rpc ListRaces(ListRacesRequest) returns (ListRacesResponse);
      }
      
      message ListRacesRequest {
        int32 limit = 1 [default = 100];
      }
      
      message ListRacesResponse {
        repeated Race races = 1;
      }
      
      message Race {
        string name = 1;
        string location = 2;
        google.protobuf.Timestamp start_time = 3;
        int32 laps = 4;
        string championship = 5;
      }
    • 5:55 - grpc-swift-proto-generator-config.json

      {
          "generate": {
              "clients": true,
              "servers": false,
              "messages": true
          }
      }
    • 6:24 - Add gRPC imports

      import GRPCCore
      import GRPCNIOTransportHTTP2
      import SwiftProtobuf
    • 6:38 - Create a gRPC client connected to a local server

      .task {
          do {
              try await withGRPCClient(
                  transport: .http2NIOTS(
                      address: .ipv4(host: "127.0.0.1", port: 8080),
                      transportSecurity: .tls
                  )
              ) { client in
                  <#code#>
              }
          } catch {
              print("gRPC error: \(error)")
          }
      }
    • 7:14 - Call the ListRaces RPC and update the view

      .task {
          do {
              try await withGRPCClient(
                  transport: .http2NIOTS(
                      address: .ipv4(host: "127.0.0.1", port: 8080),
                      transportSecurity: .tls
                  )
              ) { client in
                  let kart = SwiftKartService.Client(wrapping: client)
                  let request = ListRacesRequest()
                  let response = try await kart.listRaces(request)
                  self.races = response.races.map { race in
                      RaceInfo(
                          name: race.name,
                          location: race.location,
                          startTime: race.startTime.date,
                          championship: race.championship,
                          laps: Int(race.laps),
                          drivers: race.drivers
                      )
                  }
              }
          } catch {
              print("gRPC error: \(error)")
          }
      }
    • 8:30 - ClientManager.swift

      import GRPCCore
      import GRPCNIOTransportHTTP2
      import Synchronization
      import SwiftUI
      
      @Observable
      final class ClientManager: Sendable {
          fileprivate let state = Mutex(State.disconnected)
      
          static func makeTransport() throws -> HTTP2ClientTransport.TransportServices {
              try .http2NIOTS(
                  target: .ipv4(address: "127.0.0.1", port: 8080),
                  transportSecurity: .plaintext
              )
          }
      
          func withClient(
              body: (_ client: GRPCClient<HTTP2ClientTransport.TransportServices>) async throws -> Void
          ) async throws {
              let client = try connectIfNecessary()
              try await body(client)
          }
      
          private func connectIfNecessary() throws -> GRPCClient<HTTP2ClientTransport.TransportServices> {
              try self.state.withLock { state in
                  try state.connectIfNecessary()
              }
          }
      
          func disconnect() {
              let client = self.state.withLock { state in
                  state.disconnect()
              }
      
              client?.beginGracefulShutdown()
          }
      }
      
      extension ClientManager {
          enum State {
              case connected(GRPCClient<HTTP2ClientTransport.TransportServices>, Task<Void, any Error>)
              case disconnected
          }
      }
      
      extension ClientManager.State {
          mutating func connectIfNecessary() throws -> GRPCClient<HTTP2ClientTransport.TransportServices> {
              switch self {
              case .connected(let client, _):
                  return client
      
              case .disconnected:
                  let client = try GRPCClient(transport: ClientManager.makeTransport())
                  let task = Task { try await client.runConnections() }
                  self = .connected(client, task)
                  return client
              }
          }
      
          mutating func disconnect() -> GRPCClient<HTTP2ClientTransport.TransportServices>? {
              switch self {
              case .connected(let client, _):
                  self = .disconnected
                  return client
              case .disconnected:
                  return nil
              }
          }
      }
    • 8:39 - Propagate ClientManager to child views

      import SwiftUI
      
      @main
      struct SwiftKartApp: App {
          let manager = ClientManager()
      
          var body: some Scene {
              WindowGroup {
                  RaceScheduleView()
                      .environment(manager)
              }
          }
      }
    • 8:52 - Disconnect ClientManager when the scene enters the background phase

      import SwiftUI
      
      @main
      struct SwiftKartApp: App {
          let manager = ClientManager()
          @Environment(\.scenePhase) private var scenePhase
      
          var body: some Scene {
              WindowGroup {
                  RaceScheduleView()
                      .environment(manager)
              }
              .onChange(of: scenePhase) { _, newPhase in
                  switch newPhase {
                  case .background :
                      manager.disconnect()
                  case .inactive, .active:
                      break
                  @unknown default:
                      break
                  }
              }
          }
      }
    • 9:12 - Inject ClientManager into the view via @Environment

      @Environment(ClientManager.self) var manager
    • 9:21 - Replace withGRPCClient with manager.withClient

      .task {
          do {
              try await manager.withClient { client in
                  let kart = SwiftKartService.Client(wrapping: client)
                  let request = ListRacesRequest()
                  let response = try await kart.listRaces(request)
                  self.races = response.races.map { race in
                      RaceInfo(
                          name: race.name,
                          location: race.location,
                          startTime: race.startTime.date,
                          championship: race.championship,
                          laps: Int(race.laps),
                          drivers: race.drivers
                      )
                  }
              }
          } catch {
              print("gRPC error: \(error)")
          }
      }
    • 9:41 - Using SwiftProtobuf

      var race = Race()
      race.name = "Duck Pond Dash"
      race.location = "Apple Park, Cupertino"
      race.startTime = .init(roundingTimeIntervalSince1970: 1_781_198_600)
      race.laps = 6
      race.championship = "Corporate Cup"
      race.drivers = ["Monty", "Pepper", "Mycroft", "Pancakes", "Duke", "Kiko", "Sissi", "Bo"]
      
      try race.serializedBytes()
    • 12:32 - Server

      let server = GRPCServer(
          transport: .http2NIOPosix(
              address: .ipv4(host: "127.0.0.1", port: 8080),
              transportSecurity: .plaintext
          ),
          services: [Service()]
      )
      try await server.serve()
    • 12:45 - Service

      struct Service: SwiftKartService.SimpleServiceProtocol {
          private let database = RaceDB()
      
          func listRaces(
              request: ListRacesRequest,
              context: ServerContext
          ) async throws -> ListRacesResponse {
              var response = ListRacesResponse()
              response.races = await database.listRaces(atMost: request.limit)
              return response
          }
      }
    • 13:20 - swift_kart_service.proto

      edition = "2024";
      
      import "google/protobuf/duration.proto";
      import "google/protobuf/timestamp.proto";
      
      service SwiftKartService {
        rpc ListRaces(ListRacesRequest) returns (ListRacesResponse);
        rpc FollowRace(stream FollowRaceRequest) returns (stream FollowRaceResponse);
      }
      
      message ListRacesRequest {
        int32 limit = 1 [default = 100];
      }
      
      message ListRacesResponse {
        repeated Race races = 1;
      }
      
      message Race {
        string name = 1;
        string location = 2;
        google.protobuf.Timestamp start_time = 3;
        int32 laps = 4;
        string championship = 5;
        repeated string drivers = 6;
      }
      
      message FollowRaceRequest {
        string race_name = 1;
        repeated RaceEventType event_types = 2;
      }
      
      enum RaceEventType {
        RACE_EVENT_TYPE_UNSPECIFIED = 0;
        RACE_EVENT_TYPE_KART_LOCATIONS = 1;
        RACE_EVENT_TYPE_STANDINGS = 2;
      }
      
      message FollowRaceResponse {
        oneof event {
          KartLocations locations = 1;
          Standings standings = 2;
        }
      }
      
      message KartLocations {
        message Kart {
          int32 number = 1;
          double latitude = 2;
          double longitude = 3;
          google.protobuf.Timestamp recorded_at = 4;
        }
        repeated Kart karts = 1;
      }
      
      message Standings {
        message Entry {
          int32 kart_number = 1;
          google.protobuf.Duration gap_to_leader = 2;
          int32 position = 3;
          int32 lap = 4;
        }
      
        repeated Entry entries = 1;
      }
    • 14:16 - FollowRace stub

      func followRace(
          request: RPCAsyncSequence<FollowRaceRequest, any Error>,
          response: RPCWriter<FollowRaceResponse>,
          context: ServerContext
      ) async throws {
          throw RPCError(code: .unimplemented, message: "FollowRace is unimplemented")
      }
    • 14:38 - Implement the FollowRace RPC

      func followRace(
          request: RPCAsyncSequence<FollowRaceRequest, any Error>,
          response: RPCWriter<FollowRaceResponse>,
          context: ServerContext
      ) async throws {
          try await withThrowingTaskGroup { group in
              var iterator = request.makeAsyncIterator()
              guard let first = try await iterator.next() else { return }
              let eventTypes = Mutex(Set(first.eventTypes))
      
              group.addTask {
                  let events = tracker.events(forRace: first.raceName).filter { event in
                      eventTypes.withLock { $0.contains(event.type) }
                  }
      
                  for await event in events {
                      var message = FollowRaceResponse()
                      switch event {
                      case .locations(let locations):
                          message.locations.karts = locations.map { location in
                              var kart = KartLocations.Kart()
                              kart.number = Int32(location.number)
                              kart.latitude = location.latitude
                              kart.longitude = location.longitude
                              return kart
                          }
                      case .standings(let standings):
                          message.standings.entries = standings.map { standing in
                              var entry = Standings.Entry()
                              entry.gapToLeader = .init(rounding: standing.delta, rule: .towardZero)
                              entry.kartNumber = Int32(standing.kartNumber)
                              entry.lap = Int32(standing.lap)
                              entry.position = Int32(standing.position)
                              return entry
                          }
                      }
      
                      try await response.write(message)
                  }
              }
      
              while let next = try await iterator.next() {
                  eventTypes.withLock { $0 = Set(next.eventTypes) }
              }
      
              group.cancelAll()
          }
      }
    • 16:39 - swift_kart_service.proto

      edition = "2024";
      
      import "google/protobuf/duration.proto";
      import "google/protobuf/timestamp.proto";
      
      service SwiftKartService {
        rpc ListRaces(ListRacesRequest) returns (ListRacesResponse);
        rpc FollowRace(stream FollowRaceRequest) returns (stream FollowRaceResponse);
      }
      
      message ListRacesRequest {
        int32 limit = 1 [default = 100];
      }
      
      message ListRacesResponse {
        repeated Race races = 1;
      }
      
      message Race {
        string name = 1;
        string location = 2;
        google.protobuf.Timestamp start_time = 3;
        int32 laps = 4;
        string championship = 5;
        repeated string drivers = 6;
      }
      
      message FollowRaceRequest {
        string race_name = 1;
        repeated RaceEventType event_types = 2;
      }
      
      enum RaceEventType {
        RACE_EVENT_TYPE_UNSPECIFIED = 0;
        RACE_EVENT_TYPE_KART_LOCATIONS = 1;
        RACE_EVENT_TYPE_STANDINGS = 2;
      }
      
      message FollowRaceResponse {
        oneof event {
          KartLocations locations = 1;
          Standings standings = 2;
        }
      }
      
      message KartLocations {
        message Kart {
          int32 number = 1;
          double latitude = 2;
          double longitude = 3;
          google.protobuf.Timestamp recorded_at = 4;
        }
        repeated Kart karts = 1;
      }
      
      message Standings {
        message Entry {
          int32 kart_number = 1;
          google.protobuf.Duration gap_to_leader = 2;
          int32 position = 3;
          int32 lap = 4;
        }
      
        repeated Entry entries = 1;
      }
    • 16:40 - swift_kart_service.proto

      edition = "2024";
      
      import "google/protobuf/timestamp.proto";
      
      service SwiftKartService {
        rpc ListRaces(ListRacesRequest) returns (ListRacesResponse);
      }
      
      message ListRacesRequest {
        int32 limit = 1 [default = 100];
      }
      
      message ListRacesResponse {
        repeated Race races = 1;
      }
      
      message Race {
        string name = 1;
        string location = 2;
        google.protobuf.Timestamp start_time = 3;
        int32 laps = 4;
        string championship = 5;
        repeated string drivers = 6;
      }
    • 16:56 - Navigation link to LiveStreamView

      NavigationLink(destination: LiveStreamView(race: race)) {
          Text("Live stream")
      }
    • 17:32 - Call the FollowRace RPC in the LiveStreamView

      import SwiftUI
      import GRPCCore
      import GRPCNIOTransportHTTP2
      import SwiftProtobuf
      
      struct LiveStreamView: View {
          private let race: RaceInfo
      
          @Environment(ClientManager.self) var manager
          @State private var tracking: KartTrackingViewModel
          @State private var standings: [StandingsEntry] = []
          @State private var showLeaderboard = false
          @State private var continuation: AsyncStream<Bool>.Continuation?
      
          init(race: RaceInfo) {
              self.race = race
              self.tracking = KartTrackingViewModel(race: race)
          }
      
          var body: some View {
              VStack {
                  KartTrackingMapView(viewModel: tracking)
                      .ignoresSafeArea()
                      .onAppear { tracking.start() }
                      .onDisappear { tracking.stop() }
              }
              .onChange(of: showLeaderboard) { _, newValue in
                  continuation?.yield(newValue)
              }
              .sheet(isPresented: $showLeaderboard) {
                  LeaderboardView(race: race, standings: standings)
                      .presentationDetents([.fraction(0.3), .medium, .large])
                      .presentationBackgroundInteraction(.enabled)
              }
              .toolbar {
                  Toggle(isOn: $showLeaderboard) {
                      Label("Leaderboard", systemImage: "list.number")
                  }
              }
              .toolbarBackgroundVisibility(.visible, for: .navigationBar)
              .task {
                  do {
                      let (stream, continuation) = AsyncStream.makeStream(of: Bool.self)
                      self.continuation = continuation
                      continuation.yield(showLeaderboard)
      
                      try await manager.withClient { client in
                          let kart = SwiftKartService.Client(wrapping: client)
                          try await kart.followRace { requestStream in
                              for await showLeaderboard in stream {
                                  var message = FollowRaceRequest()
                                  message.raceName = race.name
                                  message.eventTypes = [.kartLocations]
                                  if showLeaderboard {
                                      message.eventTypes.append(.standings)
                                  }
                                  try await requestStream.write(message)
                              }
                          } onResponse: { responseStream in
                              for try await message in responseStream.messages {
                                  if let event = message.event {
                                      await handleEvent(event)
                                  }
                              }
                          }
      
                      }
                  } catch {
                      print("gRPC error: \(error)")
                  }
              }
          }
      
          @MainActor
          private func handleEvent(_ event: FollowRaceResponse.OneOf_Event) {
              switch event {
              case .locations(let locations):
                  self.tracking.updateKartCoordinates(
                      locations.karts.map {
                          TrackedKart(number: $0.number, latitude: $0.latitude, longitude: $0.longitude)
                      }
                  )
              case .standings(let standings):
                  self.standings = standings.entries.map {
                      StandingsEntry(
                          kartNumber: $0.kartNumber,
                          secondsToLeader: $0.gapToLeader.timeInterval,
                          position: $0.position,
                          lap: $0.lap
                      )
                  }
              }
          }
      }
      
      #Preview {
          NavigationStack {
              LiveStreamView(race: .example4)
                  .environment(ClientManager())
          }
      }
    • 20:55 - Containerfile

      FROM swift:latest AS builder
      
      # Copy sources into /app
      WORKDIR /app
      COPY Package.swift Package.resolved .
      COPY Sources/ Sources/
      
      # Build the server
      RUN swift build -c release --product server
      RUN cp "$(swift build -c release --show-bin-path)/server" /usr/bin/server
      
      # Copy the binary from the builder into a smaller runtime image.
      FROM swift:slim
      COPY --from=builder /usr/bin/server /usr/bin/server
      
      EXPOSE 8080
      ENTRYPOINT ["/usr/bin/server"]
    • 21:56 - Deploy service

      gcloud run deploy wwdc-demo-server \
        --image us-central1-docker.pkg.dev/wwdc26/wwdc-demo-server/wwdc-demo-server:latest \
        --region us-central1 \
        --use-http2 \
        --allow-unauthenticated
    • 22:22 - Target deployed service

      static func makeTransport() throws -> HTTP2ClientTransport.TransportServices {
          try .http2NIOTS(
              target: .dns(host: "wwdc-demo-server-863666503339.us-central1.run.app"),
              transportSecurity: .tls
          )
      }

Developer Footer

  • Vídeos
  • WWDC26
  • Desenvolva apps e serviços em tempo real com gRPC e Swift
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • AI & Machine Learning
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    • Documentation Archive
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines