{"$schema": "https://raw.githubusercontent.com/guitaripod/wwdc-sessions/master/schema/transcript.schema.json", "id": "wwdc2026-265", "language": "eng", "source": "https://developer.apple.com/videos/play/wwdc2026/265", "wordCount": 3389, "segments": [{"start": 7.0, "text": "Hi, I'm George from the Swift Server team."}, {"start": 10.0, "text": "In this video,"}, {"start": 11.0, "text": "I'll show you how you can build real time experiences"}, {"start": 14.0, "text": "in your apps and services with gRPC Swift."}, {"start": 17.0, "text": "Dynamic app experiences usually depend"}, {"start": 20.0, "text": "on fetching data from a server,"}, {"start": 22.0, "text": "but working with services can be challenging."}, {"start": 25.0, "text": "Hand crafting networking code"}, {"start": 27.0, "text": "to interact with a service can be time consuming."}, {"start": 30.0, "text": "You start with some documentation,"}, {"start": 32.0, "text": "spend time crafting great APIs"}, {"start": 34.0, "text": "and end up with something that seems to work."}, {"start": 36.0, "text": "But the documentation isn't always up-to-date,"}, {"start": 39.0, "text": "and maybe you made some mistakes along the way"}, {"start": 42.0, "text": "and the result can be something that doesn't always work as you expect."}, {"start": 45.0, "text": "Fortunately, there's a better way."}, {"start": 48.0, "text": "Many service APIs are defined separately in a specification"}, {"start": 52.0, "text": "which acts as the source of truth for the service."}, {"start": 55.0, "text": "This allows you to generate the code required to interact with it,"}, {"start": 59.0, "text": "saving you time and eliminating errors."}, {"start": 62.0, "text": "These benefits scale across all of the APIs"}, {"start": 64.0, "text": "that you need to interact with."}, {"start": 67.0, "text": "A great option for HTTP based APIs is OpenAPI."}, {"start": 72.0, "text": "It's widely used and has great support in Swift"}, {"start": 74.0, "text": "which my teammate Si talked about"}, {"start": 76.0, "text": "in the session named \"Meet Swift OpenAPI Generator\"."}, {"start": 81.0, "text": "We'll look at an alternative called gRPC."}, {"start": 84.0, "text": "Then I'll show you how you can use it in your app to make simple requests"}, {"start": 89.0, "text": "and build real-time experiences using streaming RPCs."}, {"start": 93.0, "text": "Then, we'll see how to implement a gRPC service"}, {"start": 96.0, "text": "and deploy it to the cloud."}, {"start": 99.0, "text": "First though, let's talk about what gRPC is."}, {"start": 103.0, "text": "gRPC is a framework for making remote procedure calls."}, {"start": 108.0, "text": "It's a CNCF project and widely adopted industry standard."}, {"start": 112.0, "text": "Like with OpenAPI you work with code that's generated from a specification,"}, {"start": 117.0, "text": "allowing you to start working with services quickly."}, {"start": 120.0, "text": "But in gRPC your APIs are defined in terms of functions with input and outputs,"}, {"start": 127.0, "text": "rather than in terms of HTTP."}, {"start": 130.0, "text": "Let's see how this works in practice."}, {"start": 133.0, "text": "There's a new go karting league starting nearby."}, {"start": 135.0, "text": "They've got a system which tracks everything"}, {"start": 137.0, "text": "from the schedule to all of the live race data,"}, {"start": 140.0, "text": "but they need a way to make that information available."}, {"start": 144.0, "text": "I've been working on integrating an iOS app"}, {"start": 146.0, "text": "with their backend via a gRPC service."}, {"start": 150.0, "text": "I've prepared a few views in the app"}, {"start": 152.0, "text": "and populated them with some example data."}, {"start": 155.0, "text": "I can list the upcoming races."}, {"start": 158.0, "text": "And then tap in to each to get more information."}, {"start": 161.0, "text": "But it would be great to use gRPC to get this content from the server instead."}, {"start": 166.0, "text": "A function to return the race schedule might be called list races."}, {"start": 171.0, "text": "It could be called with the number of races to request"}, {"start": 174.0, "text": "and it would return the list of races."}, {"start": 178.0, "text": "As a remote procedure call,"}, {"start": 179.0, "text": "the request message is sent by the client to the server,"}, {"start": 183.0, "text": "which executes the function"}, {"start": 184.0, "text": "and sends back the list of races as the response."}, {"start": 188.0, "text": "Let's put this theory into practice"}, {"start": 190.0, "text": "and see how to use gRPC Swift in my app."}, {"start": 194.0, "text": "I'll start by defining the service API."}, {"start": 197.0, "text": "Then I'll add the required dependencies to my Xcode project,"}, {"start": 201.0, "text": "configure the gRPC build plugin to generate the code I need to call my service."}, {"start": 206.0, "text": "And then, update my app to make a call to the server."}, {"start": 210.0, "text": "The most common format for specifying gRPC services is called Protocol Buffers,"}, {"start": 215.0, "text": "or Protobuf for short."}, {"start": 218.0, "text": "In a .proto file, I'll define the service with one RPC called ListRaces."}, {"start": 224.0, "text": "It has a ListRacesRequest as the input"}, {"start": 227.0, "text": "and a ListRacesResponse as the output."}, {"start": 230.0, "text": "The request message has one field called limit"}, {"start": 233.0, "text": "which is an integer representing the maximum number of races"}, {"start": 237.0, "text": "to include in the response."}, {"start": 239.0, "text": "I've given it a default value of 100."}, {"start": 242.0, "text": "Every field in a message is also assigned a unique field number."}, {"start": 247.0, "text": "The response message contains a repeated Race field."}, {"start": 250.0, "text": "Race is defined as a separate message and contains information such as its name,"}, {"start": 255.0, "text": "location, and championship as strings, the number of laps as an integer"}, {"start": 259.0, "text": "and the start time as a timestamp."}, {"start": 262.0, "text": "The timestamp type is one of Protobuf's Well Known Types and defined elsewhere"}, {"start": 267.0, "text": "so I need to import its definition."}, {"start": 270.0, "text": "Now that I've defined the service I can switch"}, {"start": 272.0, "text": "to Xcode to add the gRPC dependencies"}, {"start": 275.0, "text": "and configure the code generator."}, {"start": 278.0, "text": "To get started, I need to add some dependencies to my project."}, {"start": 281.0, "text": "I'll navigate to the Project Editor."}, {"start": 286.0, "text": "Then, I'll select the Package Dependencies tab"}, {"start": 288.0, "text": "and then click plus."}, {"start": 291.0, "text": "First, I'll add a dependency on grpc-swift-nio-transport"}, {"start": 295.0, "text": "which provides the high performance networking code"}, {"start": 297.0, "text": "built on top of the open source SwiftNIO library."}, {"start": 302.0, "text": "Then I'll add a dependency"}, {"start": 304.0, "text": "on grpc-swift-protobuf which provides a build plugin"}, {"start": 307.0, "text": "for generating gRPC code from my proto file."}, {"start": 313.0, "text": "Now that I've setup the dependencies,"}, {"start": 315.0, "text": "I can configure the target to use the build plugin."}, {"start": 318.0, "text": "I'll select the app Target."}, {"start": 320.0, "text": "Then, I'll select the Build Phases tab,"}, {"start": 322.0, "text": "and expand the section named Run Build Tool Plug-ins."}, {"start": 326.0, "text": "Then I'll click the plus icon,"}, {"start": 328.0, "text": "select GRPCProtobufGenerator and click Add."}, {"start": 332.0, "text": "The plugin scans the target directory for proto files"}, {"start": 336.0, "text": "and can be configured using a JSON config file."}, {"start": 339.0, "text": "I'll add those to my target now."}, {"start": 350.0, "text": "The JSON file configures what code is generated."}, {"start": 354.0, "text": "Since this is an app I only need messages and clients,"}, {"start": 357.0, "text": "I don't need the server code."}, {"start": 359.0, "text": "Now I can recompile the app to generate the code."}, {"start": 363.0, "text": "As a security measure,"}, {"start": 364.0, "text": "you'll be asked to trust the plug-in the first time you use it."}, {"start": 370.0, "text": "That's everything setup, I'm now ready to call my service."}, {"start": 374.0, "text": "I'll open up the RaceScheduleView."}, {"start": 382.0, "text": "And import the modules I need."}, {"start": 386.0, "text": "The core module provides common gRPC runtime components,"}, {"start": 390.0, "text": "the HTTP module provides the networking code,"}, {"start": 393.0, "text": "and SwiftProtobuf lets us interact with our Protobuf messages."}, {"start": 398.0, "text": "Next, I'll add a task modifier to the view"}, {"start": 401.0, "text": "in which I'll make the request."}, {"start": 402.0, "text": "Inside the task,"}, {"start": 403.0, "text": "I'm going to use the withGRPCClient function to create a client."}, {"start": 408.0, "text": "I'll do this inside a do catch block and just print the errors for now."}, {"start": 413.0, "text": "gRPC Swift lets you configure the implementation used for networking."}, {"start": 418.0, "text": "I'll use a SwiftNIO based one to connect to a server running locally on my Mac."}, {"start": 424.0, "text": "The client passed to the closure only knows about the server."}, {"start": 428.0, "text": "It doesn't know anything about the service."}, {"start": 430.0, "text": "This is where the generated code comes in:"}, {"start": 433.0, "text": "I'll create a SwiftKart client,"}, {"start": 438.0, "text": "and initialize it with the gRPC client,"}, {"start": 442.0, "text": "then I'll create the request,"}, {"start": 446.0, "text": "call the list races RPC and await the response."}, {"start": 453.0, "text": "Finally, I'll update the view with the new data"}, {"start": 456.0, "text": "by mapping the response from the server"}, {"start": 457.0, "text": "to the data model used by the view."}, {"start": 462.0, "text": "And just like that we've fetched the race schedule from our local server."}, {"start": 467.0, "text": "Finite Loops sounds fun, and it's starting soon!"}, {"start": 470.0, "text": "Before I go any further I need to make one important change."}, {"start": 475.0, "text": "At the moment, my app creates a new gRPC client every time the view appears."}, {"start": 480.0, "text": "This means each view needs to establish its own connection to the server,"}, {"start": 484.0, "text": "adding unnecessary latency."}, {"start": 487.0, "text": "Instead, my app should create a client and share it between views"}, {"start": 491.0, "text": "so that connections can be reused."}, {"start": 494.0, "text": "I can propagate the client via the app's environment."}, {"start": 498.0, "text": "Clients should also be disconnected"}, {"start": 500.0, "text": "when the app enters the background to free up resources."}, {"start": 504.0, "text": "In Xcode I'll add some code for a client manager I wrote earlier."}, {"start": 516.0, "text": "Then I'll open the app entry point and create an instance of the manager."}, {"start": 523.0, "text": "I'll make it available to child views via the environment modifier."}, {"start": 528.0, "text": "I should also disconnect the client when the scene enters the background phase."}, {"start": 532.0, "text": "To do that, I'll create a scene phase property"}, {"start": 536.0, "text": "and then watch it for changes."}, {"start": 540.0, "text": "My manager class connects lazily"}, {"start": 542.0, "text": "when asked for a client so I don't need to do anything"}, {"start": 545.0, "text": "when the scene enters the active state."}, {"start": 547.0, "text": "Now I'll use the manager in the RaceScheduleView."}, {"start": 551.0, "text": "I'll add it to the view,"}, {"start": 555.0, "text": "and make it available in the preview."}, {"start": 560.0, "text": "Finally I'll replace the withGRPCClient call with a call to the manager."}, {"start": 568.0, "text": "That's my app setup and talking to my service using code"}, {"start": 572.0, "text": "generated from a service API defined in Protobuf."}, {"start": 576.0, "text": "In addition to the Service API,"}, {"start": 578.0, "text": "Protobuf provides a message interchange format."}, {"start": 582.0, "text": "SwiftProtobuf has a code generator"}, {"start": 585.0, "text": "so that you can work directly with Swift types"}, {"start": 587.0, "text": "that represent your messages."}, {"start": 589.0, "text": "For example I can create a race message"}, {"start": 591.0, "text": "and populate the fields with the relevant information."}, {"start": 595.0, "text": "When gRPC sends messages between the client and server,"}, {"start": 598.0, "text": "it serializes them to a binary representation."}, {"start": 602.0, "text": "It uses the unique field number rather than name to identify each field."}, {"start": 607.0, "text": "As a result, the Protobuf message"}, {"start": 609.0, "text": "is roughly half the size of the equivalent JSON message."}, {"start": 613.0, "text": "Reducing message size is great for mobile apps"}, {"start": 617.0, "text": "where minimizing data transfer helps improve"}, {"start": 619.0, "text": "the performance of your network calls."}, {"start": 621.0, "text": "This is especially important when network conditions are poor."}, {"start": 626.0, "text": "This efficiency is great for other environments too,"}, {"start": 629.0, "text": "like service-to-service communication."}, {"start": 632.0, "text": "And interprocess communication"}, {"start": 634.0, "text": "like in Apple's open source Containerization framework."}, {"start": 637.0, "text": "It uses gRPC Swift to communicate over virtual sockets"}, {"start": 642.0, "text": "between the host operating system and a Linux Virtual Machine."}, {"start": 646.0, "text": "gRPC Swift is also a key component in cloud services"}, {"start": 650.0, "text": "like Private Cloud Compute,"}, {"start": 651.0, "text": "iCloud Keychain and Photos, and SharePlay file sharing."}, {"start": 655.0, "text": "But our use doesn't just power external facing services,"}, {"start": 659.0, "text": "gRPC runs deep into our internal infrastructure,"}, {"start": 662.0, "text": "such as in our OS build and release systems."}, {"start": 666.0, "text": "One of gRPC's standout features is its first class support for streaming"}, {"start": 671.0, "text": "Many RPCs, like list races,"}, {"start": 674.0, "text": "simply send a single request message to the server"}, {"start": 677.0, "text": "which replies with a single response message."}, {"start": 680.0, "text": "This is called a unary RPC."}, {"start": 683.0, "text": "But RPCs can stream request and response messages,"}, {"start": 686.0, "text": "meaning there are three other types of RPC to explore."}, {"start": 690.0, "text": "A client streaming RPC is when the client sends any number of messages to the server"}, {"start": 695.0, "text": "which replies with a single response message."}, {"start": 698.0, "text": "Imagine each go kart streaming its telemetry data to the server."}, {"start": 702.0, "text": "In server streaming RPCs the client sends a single request message to the server"}, {"start": 707.0, "text": "which replies with any number of response messages."}, {"start": 711.0, "text": "Think about real-time updates, like a live text commentary feed."}, {"start": 715.0, "text": "The final type is bidirectional streaming"}, {"start": 718.0, "text": "where the client and server can send each other any number of messages."}, {"start": 723.0, "text": "I've got a great idea for how I can use this in my app"}, {"start": 725.0, "text": "to provide live race updates."}, {"start": 728.0, "text": "The request messages will tell the server"}, {"start": 730.0, "text": "what type of events the client has subscribed to,"}, {"start": 733.0, "text": "and the response messages will contain the relevant events."}, {"start": 737.0, "text": "The client can send more messages to the server when they change"}, {"start": 740.0, "text": "what events they're interested in receiving."}, {"start": 745.0, "text": "My app has been making requests to a server running on my Mac"}, {"start": 748.0, "text": "which is also written in Swift."}, {"start": 750.0, "text": "Let's take a look."}, {"start": 753.0, "text": "Setting up the server is straightforward."}, {"start": 755.0, "text": "I create a server object"}, {"start": 757.0, "text": "initialized with a transport,"}, {"start": 759.0, "text": "and the services it should offer."}, {"start": 761.0, "text": "To start the server, I just call serve."}, {"start": 765.0, "text": "The service is just a type"}, {"start": 767.0, "text": "that implements a protocol generated by the build plugin."}, {"start": 771.0, "text": "You can see the list races RPC I implemented earlier:"}, {"start": 775.0, "text": "it's an async function"}, {"start": 776.0, "text": "which takes a request and returns a response."}, {"start": 780.0, "text": "Implementing it is just a matter of querying the database for races,"}, {"start": 784.0, "text": "populating the message and then returning it."}, {"start": 787.0, "text": "To incorporate the streaming RPC, I'll update the service definition,"}, {"start": 791.0, "text": "then I'll switch to the server and regenerate the code"}, {"start": 794.0, "text": "so I can implement the new RPC."}, {"start": 796.0, "text": "And once that's done, I'll update the app to call it."}, {"start": 800.0, "text": "I'll start by adding a FollowRace RPC to the service definition."}, {"start": 805.0, "text": "Because the RPC streams request and response messages,"}, {"start": 809.0, "text": "I need to add the stream keyword before the input and output."}, {"start": 813.0, "text": "Then I need to define the messages."}, {"start": 816.0, "text": "The request message includes the name of the race to follow"}, {"start": 819.0, "text": "and a list of event types to subscribe to"}, {"start": 821.0, "text": "which is represented as an enum."}, {"start": 824.0, "text": "The response type has a oneof field"}, {"start": 826.0, "text": "which is just like a Swift enum with associated values."}, {"start": 830.0, "text": "The message can either hold the locations of each kart"}, {"start": 833.0, "text": "or the current race standings,"}, {"start": 835.0, "text": "which are defined as separate messages."}, {"start": 838.0, "text": "Now that the service definition has been updated,"}, {"start": 841.0, "text": "I'll switch to the server in Xcode"}, {"start": 843.0, "text": "so I can work on implementing the new RPC."}, {"start": 846.0, "text": "I'll build the project to regenerate the code."}, {"start": 852.0, "text": "I have a build error now because the protocol has a new requirement"}, {"start": 855.0, "text": "that I haven't implemented yet, so I'll fill out a stub."}, {"start": 861.0, "text": "This looks a bit different to the list races RPC because of the streaming."}, {"start": 865.0, "text": "The request parameter is an async sequence of request messages"}, {"start": 869.0, "text": "and the response parameter is an object"}, {"start": 871.0, "text": "for writing response messages to the client."}, {"start": 874.0, "text": "I know that I need to handle two streams of data simultaneously"}, {"start": 877.0, "text": "so I'll need a task group."}, {"start": 881.0, "text": "I need to wait for the first request message"}, {"start": 883.0, "text": "so that I know the name of the race to track"}, {"start": 885.0, "text": "and which events the caller is interested in."}, {"start": 888.0, "text": "I'll create an async iterator and await the first message."}, {"start": 892.0, "text": "I'll store the events in a set protected by a mutex"}, {"start": 896.0, "text": "because two different tasks will need to access it concurrently."}, {"start": 900.0, "text": "Then I'll add a task to the task group"}, {"start": 903.0, "text": "which calls the live race tracker with the name of the race to follow."}, {"start": 910.0, "text": "This gives me an async sequence of events which I can then filter"}, {"start": 914.0, "text": "to only include the ones the client is currently interested in."}, {"start": 921.0, "text": "I'll iterate the filtered events"}, {"start": 925.0, "text": "and create an empty response message."}, {"start": 931.0, "text": "Then, I'll switch over the event and populate the message."}, {"start": 935.0, "text": "First, I'll map the array of kart locations from the tracker"}, {"start": 939.0, "text": "to the data type used by the RPC."}, {"start": 942.0, "text": "Then, I'll do the same for the standings."}, {"start": 946.0, "text": "Now I'll write the message to the client."}, {"start": 951.0, "text": "There are a few things left to do:"}, {"start": 954.0, "text": "the first is to continue consuming the request messages"}, {"start": 957.0, "text": "as the caller might change what events they're interested in."}, {"start": 960.0, "text": "We'll use the end of the request stream"}, {"start": 962.0, "text": "as a signal that the client no longer wants any more events,"}, {"start": 965.0, "text": "so we can cancel the tasks running in the task group"}, {"start": 968.0, "text": "to stop sending back messages."}, {"start": 971.0, "text": "Finally I'll restart my server so that the client can call the new RPC."}, {"start": 978.0, "text": "That's the RPC implemented in the service, now I can update the app."}, {"start": 983.0, "text": "I'll open the Xcode project for the app,"}, {"start": 984.0, "text": "and update the proto file to include the new RPC and messages."}, {"start": 1004.0, "text": "I'll build the project to regenerate the gRPC code."}, {"start": 1010.0, "text": "Then I'll navigate to the RaceInfoView."}, {"start": 1014.0, "text": "and add a NavigationLink to a LiveStreamView that I created earlier."}, {"start": 1019.0, "text": "Then I'll open up the live stream view."}, {"start": 1026.0, "text": "It displays a map that will draw annotations"}, {"start": 1028.0, "text": "representing the positions of each kart in the race."}, {"start": 1032.0, "text": "There's also a toolbar button which opens a sheet"}, {"start": 1034.0, "text": "to display a live leaderboard."}, {"start": 1037.0, "text": "The showLeaderboard property tracks whether this is displayed or not."}, {"start": 1041.0, "text": "The view already has properties"}, {"start": 1043.0, "text": "to store the various bits of state I'm interested in,"}, {"start": 1046.0, "text": "I just need to call the RPC"}, {"start": 1047.0, "text": "and wire up the data received from the server."}, {"start": 1051.0, "text": "First, I'll add the imports I used earlier."}, {"start": 1054.0, "text": "Then I'll inject the client via the environment."}, {"start": 1062.0, "text": "Like before I'll create a task,"}, {"start": 1067.0, "text": "and call manager.withClient."}, {"start": 1071.0, "text": "Then I'll create a kart client"}, {"start": 1078.0, "text": "and call the FollowRace RPC."}, {"start": 1084.0, "text": "Its structure is different to the unary list races RPC."}, {"start": 1088.0, "text": "It has two closures, one for writing request messages"}, {"start": 1091.0, "text": "and another for handling response messages."}, {"start": 1094.0, "text": "I need to send a request message every time the value of showLeaderboard changes."}, {"start": 1100.0, "text": "I'll use an AsyncStream to track it over time"}, {"start": 1102.0, "text": "and store its continuation as a property."}, {"start": 1106.0, "text": "When showLeaderboard changes,"}, {"start": 1108.0, "text": "I'll yield the new value to the continuation."}, {"start": 1112.0, "text": "I'll create the AsyncStream and its continuation in the task."}, {"start": 1119.0, "text": "I'll need to yield the current value of showLeaderboard to the stream"}, {"start": 1122.0, "text": "as the initial value."}, {"start": 1125.0, "text": "In the first closure of the RPC I can iterate the stream,"}, {"start": 1130.0, "text": "and send a message to the server for each value."}, {"start": 1135.0, "text": "If the leaderboard is being shown I'll add the standings event."}, {"start": 1142.0, "text": "And then I'll write the message to the server."}, {"start": 1147.0, "text": "In the response closure,"}, {"start": 1148.0, "text": "I'll iterate the messages and update the view state for each event."}, {"start": 1151.0, "text": "I'll use a helper method for handling the events."}, {"start": 1156.0, "text": "I'll switch over the event"}, {"start": 1157.0, "text": "and map each to the data type used by the view."}, {"start": 1162.0, "text": "I'll start with the kart locations."}, {"start": 1164.0, "text": "And then I'll do the same for the standings"}, {"start": 1169.0, "text": "Finally, I'll iterate the response messages"}, {"start": 1171.0, "text": "and call the helper for each event."}, {"start": 1185.0, "text": "Let's check it out."}, {"start": 1189.0, "text": "It looks like a race is about to start at Apple Park."}, {"start": 1193.0, "text": "They're heading down towards the Rainbow Arches"}, {"start": 1196.0, "text": "and now it looks like they're turning right towards the Duck Pond."}, {"start": 1200.0, "text": "Monty's in the lead, followed closely by Pepper and Bo."}, {"start": 1204.0, "text": "That's great, but I still haven't achieved my goal"}, {"start": 1206.0, "text": "of making the information available to spectators"}, {"start": 1209.0, "text": "because the service is running locally."}, {"start": 1211.0, "text": "Let's deploy it to the cloud"}, {"start": 1213.0, "text": "so that it's available to everyone using the app."}, {"start": 1216.0, "text": "I'm using Google Cloud Platform to host my service,"}, {"start": 1219.0, "text": "but you could use another platform like AWS or Fly.io."}, {"start": 1224.0, "text": "The approach will be similar but the exact steps will be different."}, {"start": 1228.0, "text": "Most servers run Linux and that's what I'll deploy to today."}, {"start": 1232.0, "text": "I don't need to make any code changes"}, {"start": 1234.0, "text": "but I do need to package up the server executable"}, {"start": 1237.0, "text": "into a container image with its runtime dependencies."}, {"start": 1241.0, "text": "Then I'll publish the image to my cloud provider's image registry."}, {"start": 1246.0, "text": "After that I'll create a deployment."}, {"start": 1248.0, "text": "Finally I'll update the app to target the deployed service."}, {"start": 1253.0, "text": "I'll start by creating a Containerfile"}, {"start": 1255.0, "text": "which describes the steps required to build the container image."}, {"start": 1260.0, "text": "I'll use swift:latest as the base image."}, {"start": 1263.0, "text": "Next, I'll set the working directory,"}, {"start": 1265.0, "text": "and copy over the package manifest and source files."}, {"start": 1269.0, "text": "Then I'll build the server in release mode and copy it into a known location."}, {"start": 1274.0, "text": "At this point, I have the server executable in my image,"}, {"start": 1278.0, "text": "but it also contains the whole Swift toolchain."}, {"start": 1281.0, "text": "I don't need all of that to run my server"}, {"start": 1283.0, "text": "and it makes the image much larger than it needs to be."}, {"start": 1287.0, "text": "I'll use a multi-stage build"}, {"start": 1289.0, "text": "and copy the binary into a swift:slim runtime image."}, {"start": 1293.0, "text": "Finally, I expose a port and set the entry point to be the server."}, {"start": 1298.0, "text": "That's the Containerfile written,"}, {"start": 1300.0, "text": "at this point I would build and publish the image"}, {"start": 1303.0, "text": "to a container registry"}, {"start": 1305.0, "text": "but that will take a few minutes,"}, {"start": 1306.0, "text": "so I'll use one I published earlier."}, {"start": 1309.0, "text": "In the terminal I can use the gcloud run deploy command."}, {"start": 1315.0, "text": "I'll provide the name of the deployment, the image name and the region."}, {"start": 1320.0, "text": "Then I need to specify that my service uses http2"}, {"start": 1324.0, "text": "and allows unauthenticated requests."}, {"start": 1328.0, "text": "When the deployment finishes, it prints out the URL for the service"}, {"start": 1331.0, "text": "which I'll need when I update the client, so I'll copy it now."}, {"start": 1337.0, "text": "I'll switch back to the app and open the ClientManager."}, {"start": 1341.0, "text": "I'll update the connect target to the DNS name of the service"}, {"start": 1344.0, "text": "from the deploy command."}, {"start": 1346.0, "text": "Then I'll enable TLS by changing the transport security option"}, {"start": 1350.0, "text": "from plaintext to TLS."}, {"start": 1357.0, "text": "Let's test it out."}, {"start": 1368.0, "text": "It looks like we just made the start of the Finite Loops race."}, {"start": 1373.0, "text": "Oh what a disastrous start for Pepper,"}, {"start": 1375.0, "text": "as Monty takes first position, followed closely by Mycroft and Kiko."}, {"start": 1380.0, "text": "The drivers are turning into the Infinite Loop campus."}, {"start": 1384.0, "text": "Pepper's gained back a few positions."}, {"start": 1387.0, "text": "It looks like we're in for a great race here."}, {"start": 1391.0, "text": "I've shown you how to use gRPC Swift"}, {"start": 1393.0, "text": "to build great live experiences in your apps,"}, {"start": 1396.0, "text": "and how it can simplify app-to-server communication"}, {"start": 1399.0, "text": "from defining a service, generating code,"}, {"start": 1402.0, "text": "all the way through to implementing and deploying a service to the cloud."}, {"start": 1406.0, "text": "And that's just the start."}, {"start": 1408.0, "text": "gRPC Swift has plenty of built in features"}, {"start": 1411.0, "text": "to help you take your application from prototype to production"}, {"start": 1415.0, "text": "Whether that's integration with other Swift packages"}, {"start": 1418.0, "text": "like Swift OTel or Swift service lifecycle."}, {"start": 1421.0, "text": "Or advanced connection management features like custom transports and name resolvers,"}, {"start": 1426.0, "text": "and client side load balancing."}, {"start": 1428.0, "text": "You're now ready to use gRPC in your app:"}, {"start": 1432.0, "text": "why not prototype part of the app to server interactions"}, {"start": 1435.0, "text": "and see how simple gRPC Swift makes the workflow."}, {"start": 1439.0, "text": "Or you could try out one of the tutorials"}, {"start": 1441.0, "text": "and examples in the project's repository available on GitHub."}, {"start": 1446.0, "text": "Because the project is open source,"}, {"start": 1447.0, "text": "you can also contribute, whether that's asking questions,"}, {"start": 1450.0, "text": "improving documentation, or proposing and implementing new features."}, {"start": 1455.0, "text": "Thank you for watching and see you on track!"}]}