Streaming is available in most browsers,
and in the Developer app.
-
A Swift Tour: Explore Swift’s features and design
Learn the essential features and design philosophy of the Swift programming language. We'll explore how to model data, handle errors, use protocols, write concurrent code, and more while building up a Swift package that has a library, an HTTP server, and a command line client. Whether you're just beginning your Swift journey or have been with us from the start, this talk will help you get the most out of the language.
Chapters
- 0:00 - Introduction
- 0:51 - Agenda
- 1:05 - The example
- 1:32 - Value types
- 4:26 - Errors and optionals
- 9:47 - Code organization
- 11:58 - Classes
- 14:06 - Protocols
- 18:33 - Concurrency
- 23:13 - Extensibility
- 26:55 - Wrap up
Resources
- Forum: Programming Languages
- The Swift Programming Language
- Tools used: Ubuntu
- Tools used: Visual Studio Code
- Tools used: Windows
- Value and Reference types
- Wrapping C/C++ Library in Swift
Related Videos
WWDC23
WWDC22
WWDC21
-
Download
Hi, I’m Allan Shortlidge and I work on the Swift compiler. Today, I’m excited to give you a tour of my favorite programming language: Swift.
Swift is a modern programming language. It is feature rich, has incredible performance, and doesn’t compromise on safety. Its lightweight syntax makes it a joy to program in, and its powerful features can make you super productive.
Swift is best known as the premier language for writing apps that run on Apple’s devices, but it can be used for so much more.
As a cross-platform systems language, Swift is great for writing server applications, too. And with the Embedded Swift effort that began last year, Swift can scale down to the most resource constrained environments, like the chips powering smart home devices. Today we’re going to take a tour of the core features of Swift. We won’t cover every aspect of it or go deep on any one topic. Our goal is to become familiar with the language and to learn the design principles that make Swift unique.
While I introduce you to Swift, I’m going to demonstrate its features by building infrastructure for the next great social network. The code will be organized into a Swift package that has three components: The first is a library that provides the data model for representing users in a graph.
The second component is an HTTP server that can respond to graph queries. And lastly, I’ll introduce a command line utility that can send requests to our server.
To kick off this tour of Swift, let’s start with a fundamental programming concept: Representing data.
The primary way you represent data in Swift is with value types. I’ll show you what I mean with some code. In Swift we can use the var keyword to introduce a variable, like this.
Let’s declare a second variable, y and assign it the value of x. What happens noq if I change the value of x? In the output, the value of y is still one.
This probably doesn’t surprise you; this is the way that integer types work in most languages. However, it illustrates what we mean when we say a type has value semantics. Value types have a few important properties: Instances of a value type don’t share state, so changes to one value can’t effect other values of the same type.
They also don’t have identity. This means that if two values are equal then the values are interchangeable.
Basic types like integers, booleans, and floating point numbers are value types in most languages. In Swift, though, value types are everywhere. Another way that we can make our code easier to reason about is by controlling the mutability of data. I introduced x and y with the var keyword, which makes them mutable. If I use let instead of var, though, Swift will guarantee that this value doesn’t change.
Let’s build a more complex data type by introducing a model for Users in our graph.
Swift has structs, which aggregate multiple values into a single value. I’ll create one and call it User.
This struct will need a few properties. One for a username, another controlling whether the user is visible, and finally a list of friends, which I’ve represented using an array of username strings.
I’ll create a user named Alice and give her some friends.
Notice that I didn’t declare Alice with a type. Swift is a typesafe language, and every variable has a type at compile time, but here the type can be inferred to User so that I don’t have to write it. Next, I’ll create a user named Bruno and give him Alice’s friends.
What happens now if I give Alice another friend? The output shows that Alice and Bruno have different friends. That’s because arrays are value types in Swift. When I assigned Alice’s friends to Bruno’s friends, the array was copied. Since the User struct is composed of value types, it is automatically a value type itself. Most types you encounter in typical Swift code are value types. Reference types, like classes, do exist and we’ll cover them later, but their uses are more specialized. Swift emphasizes value types and immutability because controlling when a value can change makes it much easier to reason about code, especially in tricky domains like concurrent programming.
Next up, it’s time to talk about errors. Errors are a part of everyday programming. Disks fill up, network connections fail, and users provide bad data. And yet, your program needs to keep working while informing the user when something goes wrong. Swift provides an error handling model that makes it easy to report errors and gracefully handle them. Swift’s philosophy for error handling can be summed up in three points: First, the parts of your code that can be the source of an error should be marked so they don’t surprise you.
Second, errors ought to contain enough context to act on. And finally, Swift differentiates between recoverable errors and programmer mistakes.
When a network connection fails your program should keep running. On the other hand, an out-of-bounds array access probably indicates that the code is wrong. Swift will halt the program to prevent that bug from escalating into a security issue.
Let’s see how we can make the User model safer by checking error conditions. Right now the friends array can be directly modified which might lead to invalid states like the user being friends with themselves, or the friends list containing duplicate values. I’ll create a method called addFriend so that I can validate additions to the array.
By default, methods on a struct can’t modify the struct’s properties. This method needs to be declared mutating so that it can change friends.
Now I want to prevent direct use of the friends property so I’ll give it a private setter and switch to calling addFriend instead.
Next, I’d like to report errors from addFriend, which means that I need a way to represent those errors. A Swift enum makes a great error type.
An enum represents a choice between different cases and can enumerate all of the possible causes of an error. I just need to conform the enum to the Error protocol.
We’ll cover protocols in more detail later. Next we can check whether the input to addFriend is invalid and throw an error if it is. The compiler is letting us know that the method needs to be marked throws now because it is a source of errors.
I could have checked the input with if statements, but Swift’s guard statements are perfect for detecting error conditions, because they require that we return from the function if the guard condition isn’t met. Now that addFriend is throwing, there’s a diagnostic indicating that we have a source of errors that hasn’t been handled.
We need to mark the call with the try keyword to indicate that an error might occur.
I want to see what the error looks like, so I’ll trigger one and observe it by wrapping the call in a do/catch block.
Great, the duplicateFriend error was thrown. One problem with this error, though, is that it doesn’t tell me which friend is a duplicate. I can add that context by giving this case an associated value.
Now the username is printed with the error.
Next, let’s prototype a query that finds a user given their username.
I need a place to store users, so I’ve created a dictionary that maps usernames to user structs.
Our query function needs a way to handle the case where a user is not found. It could throw an error, but another option would be to make the return value optional.
Swift has rich, built-in support for optional values. An optional value is either nil or it’s a valid value of whatever type the optional is wrapping. To get to the value stored in an optional, you must unwrap it.
By requiring that your code handles both nil and non-nil values, Swift prevents a common mistake you can make in other languages where an unexpectedly nil value causes your program to crash. Let’s see how optionals can be used in our code.
In the findUser function I can directly return the result of a lookup into this dictionary.
There’s an error here because Dictionary’s subscript operator returns an optional User, which makes sense since there may not be a value corresponding to the key.
I’ll update the function return type to be optional by adding a question mark. Let’s try calling findUser.
Since it returns an optional, we need to unwrap the result to work with the User. One of the most common ways to unwrap an optional in Swift is to use the if let syntax. If there is a non-nil value it will be bound to the let in the body of this if statement. In a situation where I’m 100% sure, that there will always be a valid value at runtime, I can also use an exclamation point to force unwrap an optional.
Swift will check at runtime that my assumption is correct.
Whoops! There wasn’t a user named dash in the dictionary so the program was halted. I’ll be more careful next time.
Error handling and unwrapping optionals in Swift share something in common. Both are designed to ensure that your code is structured to handle, all of the possibilities.
Each of the places in your program that may encounter an error must either catch or propagate the error, and the throws and try keywords show you exactly where that occurs. And when working with an optional value, you must verify the value exists by unwrapping it before use.
The design of errors and optionals make it easier to write correct, debuggable programs in Swift.
I’ve outlined a basic data model for the social graph and now I think it’s time to start adding some structure to the code. Two units of code organization supported by Swift are modules and packages.
A module in Swift is comprised of a collection of source files that are always built together. Modules can also depend on other modules. For example, the module representing an app might depend on a library module that provides core functionality needed by both the app and a server. A collection of modules can be distributed as a Package.
Finally, the modules in one package can also have dependencies on modules in another package. Swift Package Manager is the tool for managing packages. You can invoke it from the command line to build, test, and run your code. You can also work on a Swift package using Xcode or VS Code.
If you’re looking for a library to accomplish a specific task, like create an HTTP server, you can probably find an open source Swift package that does it on The Swift Package Index. Later, I’m going to use a few open source packages to build my server and command line utility. Returning to the code, I’ve taken what I’ve written so far and reorganized it into a package. The first module in the package is a library module containing our social graph data model. There are also tests to accompany the library. The error enum and the User struct that we defined earlier are now in their own files. Let’s inspect the User struct. You’ll notice that I made some changes to it. It and many of its members now have the public modifier, which allows them to be used from code outside the library.
Public is one of several different access control levels available in Swift. There are also private, internal, and package levels. A declaration that is marked private can only be accessed by code in the same file. An internal declaration can only be accessed by other code in the same module. Whenever you don’t specify an access level, Swift implicitly uses internal. Package declarations are accessible from other modules in the same package. And public declarations are accessible from any other module.
Up until now I’ve only talked about value types, but sometimes you need to represent shared mutable state.
For that, Swift has reference types, like classes. Later, I’m going to build an HTTP server that stores the social graph data model. It will need to respond to requests that perform actions like adding or listing friends. When a request comes in, I want the code handling it to use an abstraction to access the collection of users. That collection needs to be shared and mutated by multiple requests, which means I should use a reference type, to encapsulate it. Let’s take a deeper look at classes with a simpler example. If you’ve programmed in an object oriented language, Swift’s classes should look familiar to you. Classes support single inheritance, like in this example where the Cat class inherits from its superclass Pet. Methods on a superclass can be overridden by methods on a subclass. And type conversions going from subclass to superclass, and vice-versa, work as you would expect.
Swift manages memory automatically. For reference types, Swift has a feature called Automatic Reference Counting. Behind the scenes, the compiler ensures that an object remains alive as long as there are references to it. It does this by incrementing and decrementing a reference count.
When there are no more references, the object deallocates immediately. Automatic reference counting is predictable, which is great for performance. One challenge, though, is that cycles can form, preventing objects from deallocating. Here, I have an Owner class that has an array of pets. The Pet class has a reference back to Owner which creates a cycle. To break this cycle, I can use a weak reference to avoid increasing the reference count on Owner. Notice that when we make the owner property weak, it also becomes optional. If the Owner instance deallocates before the Pet does, this property will become nil.
Earlier I said that Swift emphasizes value types, but classes have an important role too. If you need shared mutable state, an object with identity, or inheritance, then classes are the right tool for the job.
In many object oriented languages, inheritance is the main mechanism for polymorphism. In Swift, though, protocols provide a more general way to build abstractions, and they work equally well with both value and reference types.
A protocol is an abstract set of requirements for a type. We can declare that a type conforms to a protocol by providing implementations of all of the requirements. In this example StringIdentifiable is a protocol with a single requirement. The User type conforms to StringIdentifiable by providing an implementation of the identifier property in an extension.
Extensions are one of my favorite Swift features. You can use one to add methods, properties, protocol conformances to any type, regardless of where that type is defined. The collection types in the Swift standard library are a great example of a family of types that can be abstracted over using a protocol. You’ve already seen the Swift collection types Array and Dictionary, but there are more. A Set is another kind of collection that represents an unordered list of unique elements. Strings are also collections in Swift because they contain lists of Unicode characters.
Every type that conforms to Collection shares some features in common.
For example, you can iterate over the contents of any type that conforms to Collection using a for loop. You can also access a collection’s elements using an index.
The Swift standard library comes with implementations of many standard algorithms that can be used on any Collection. Algorithms like map, filter, and reduce will look familiar if you’ve worked with functional programming before. Swift also has a special shorthand syntax for closures that can make using these algorithms especially elegant. In a closure that does not explicitly name its parameters, dollar prefixed variables represent the parameters anonymously, making it easy to write succinct code. Let’s put Swift’s collection algorithms to work.
My server will have a feature where it finds people you might know by showing you the friends of your friends.
I can use collection algorithms to compute this set of users.
Here is the UserStore class, which encapsulates a dictionary of users and also has some existing methods to look up users by their usernames. I’ll add a method that queries for friendsOfFriends. To start, it needs to look up the original user. Next, I’m going to create a Set containing the usernames of the original user and their friends. This set contains the usernames that we want to exclude from our results. Now, I’ll build up the result of this method using functional programming. First, I want to map the friends’ usernames to instances of the User struct so that I can access their friends. I used compactMap to look up User structs for each friend and drop any Users that are nil. Next, we want to gather all friends of the user’s friends.
flatMap concatenates these friend lists into a new array. Finally, we need to exclude the original user and their friends. filter drops the usernames that are in the excluded set.
We’re almost done, but there’s one problem with the result: it might contain duplicate usernames. There is no algorithm in the standard library that will take our result and return only the unique elements in it, but I can implement one myself pretty easily using generics. I’ll extend Collection to add a method called uniqued. The implementation of uniqued should be simple. I can first create a Set with the contents of the collection, and then convert that Set back into an Array. This doesn’t quite work because the Set type requires that the elements stored in it conform to the Hashable protocol. That makes sense, since Set relies on hashing to efficiently store values. To meet this requirement, we can constrain our extension on Collection just collections that contain elements conforming to Hashable.
Now we can call uniqued and we’re done.
With just a few lines of code we built a useful algorithm that can apply to any Collection type. Because of the flexibility of Swift protocols, the set of types that now have a uniqued method is not limited to some hierarchy of classes. There’s a lot more you can do with protocols and generics, so if you’re curious I recommend watching, “Embrace Swift generics” and “Design protocol interfaces in Swift”.
Okay, before we move on to building an HTTP server, there’s one more important Swift concept I want to tell you about first, which is concurrency.
The fundamental unit of concurrency in Swift is a Task, which represents an independent, concurrent execution context.
Tasks are light-weight, so you can create lots of them. You can wait for a task to complete to get its result, or you can cancel one if its work becomes unnecessary.
Tasks can execute concurrently, which will make them great for handling the HTTP requests received by my server. As a task runs it may perform asynchronous operations, like reading from the disk or messaging another service and waiting for a response. While a Task is waiting for an asynchronous operation to complete, it suspends to yield the CPU to other tasks that have work to do.
To model Task suspension in code, Swift uses the async/await syntax. A function that may suspend is marked with the async keyword. When an async function is called, the await keyword is used to indicate that a suspension can occur on that line.
Let’s put Swift’s concurrency features to work in our server.
I’m going to continue building out the package we were working on earlier using VSCode in a server development environment. I’ve updated the package by creating a new target for the server. It has a dependency on Hummingbird, which is an open source HTTP server framework.
Hummingbird handles listening for requests and sending responses so that I can concentrate on my application logic. I’ve written the minimal code to begin listening for connections, but they can’t make any requests yet.
A request handler will need a reference to a UserStore. As a convenience, I’m going to extend UserStore to add a static instance to share between requests.
It looks like there’s a problem with this code. Accessing a global UserStore variable could introduce data races, because UserStore is not Sendable. Let’s dig into what that means. Suppose our server receives two simultaneous requests. The task associated with the first request needs to look up a user, while the other task is in the middle of creating a new user. Since the UserStore is shared mutable state, these tasks may access the same memory on different threads. This is a data-race, and it may lead to crashes or unpredictable behavior.
Avoiding data races in your code is important. That’s why in the Swift 6 language mode, the data race safety of your program is fully verified at compile time. One of the ways that data race safety is achieved is by requiring that the values shared between concurrency domains are Sendable. A Sendable value is one that protects its state from concurrent accesses. For example, a type might qualify as Sendable, if it acquires a lock while reading and writing mutable state. When the compiler told us that the UserStore global variable was unsafe, it was because UserStore is not known to be Sendable.
To make UserStore Sendable, we could manually add synchronization to it. However, Swift has a more convenient feature for this, called actors. Actors are similar to classes because they are also reference types that can encapsulate shared mutable state. However, actors automatically protect their state by serializing accesses. Only a single Task is allowed to execute at a time on an actor. Calls to actor methods from outside the context of the actor are asynchronous.
Now that we know a bit more about what this error is telling us, I can make concurrent access to UserStore safe by making it an actor.
Now that accesses are synchronized, the error is gone.
We can move on to writing an HTTP request handler. I’ll add a friendsOfFriends route which will take a username as an argument and return an array of strings. The handler is a closure that Hummingbird will run on an independent Task. Since UserStore is an actor and we’re accessing it from a different concurrency domain in this closure, that access is asynchronous and we need to use the await keyword.
Okay, let’s quickly test the handler by sending the server a request with curl.
There’s the response I was expecting. Thanks to Swift 6’s complete data race protection, we can be confident that the server handles concurrency correctly.
We covered the basics of writing concurrent code in Swift like Tasks, async/await, and actors but there’s much, much more to explore. As a starting point, you can check out “Explore structured concurrency in Swift”.
The final category of Swift features we’ll cover have to do with extending the language. These powerful features are often used by library authors to build expressive, type-safe APIs and eliminate boilerplate code in your applications.
The first feature is property wrappers. These wrapper types encapsulate logic for managing stored values. By intercepting the calls to read and write a property, they implement reusable logic that can be applied to a property with a simple annotation. In the example, the Argument property wrapper from the swift-argument-parser package has been applied to the username property. The Argument wrapper designates that the property stores the value of a command line argument. Let’s see this in action. I’ve created a new target in my package for a command line utility. The new target depends on the ArgumentParser package, which it uses to parse command line arguments. In the main file there’s a type conforming to AsyncParsableCommand which provides the top level configuration for the arguments accepted by my tool.
Let’s see this in action.
Argument parser has generated a nicely formatted help message for my tool.
So far, the only thing the tool does, though, is print this description.
Let’s change that by adding the first subcommand.
I can start by creating a new struct conforming to AsyncParsableCommand.
This command will request the friends of friends for a user, using the server route we built earlier.
I need to register it as a sub-command.
The command takes one argument, which is a username, and I’ve annotated the property for that with the Argument property wrapper.
Next, I need to fill in the implementation of the run method.
I’m leveraging a Request utility that I wrote to encapsulate the HTTP requests that this tool will send. It gets initialized with a relative path for the command, the type of data expected in the response, and some arguments in the form of a dictionary.
Since this is an HTTP get operation, invoking the get method executes the request.
This method is async since it sends a network request and the current Task should suspend while it waits for a response.
Let’s try running it.
Looks like I forgot to specify a username. Argument parser automatically produced some helpful output to explain the missing argument.
Let’s run it again and see what the response is if I specify Alice.
Great, the command is working. The Argument property wrapper made building my tool’s commands really easy and it’s a great example of the kind of expressive API you can deliver in Swift. Another language tool that library developers can leverage is result builders.
Result builders allow you to express complex values declaratively. A result builder API takes a closure in which a special, lightweight syntax is used to incrementally build up a resulting value. This feature has been used to build native UI frameworks, web page generators, and more. In the Swift standard library, Regex builders leverage this feature to provide an easy-to-read alternative to terse, regular expression strings. In addition to property wrappers and result builders, macros are another very flexible tool. Macros are Swift code that act as a compiler plugin, taking the syntax tree as input and returning transformed code as output.
If you want to learn more about Result Builders watch: “Write a DSL in Swift using result builders”. Or for macros, watch “Expand on Swift macros”.
That was our brief tour of Swift. Whether you’re new to the language or you’ve been using it for a while, I hope you’re excited to take what you’ve learned and build something really cool using Swift’s unique features. As you’re writing code, look for opportunities to pick the right tool for the job. It might be that you model something with a value type instead of a class, fully generalize an algorithm using generics, or fix a data race with an actor. Swift has all the tools you need to write elegant yet powerful code.
Thanks for joining me today and have a great WWDC!
-
-
1:49 - Integer variables
var x: Int = 1 var y: Int = x x = 42 y
-
3:04 - User struct
struct User { let username: String var isVisible: Bool = true var friends: [String] = [] } var alice = User(username: "alice") alice.friends = ["charlie"] var bruno = User(username: "bruno") bruno.friends = alice.friends alice.friends.append("dash") bruno.friends
-
3:05 - User struct error handling
struct User { let username: String var isVisible: Bool = true var friends: [String] = [] mutating func addFriend(username: String) throws { guard username != self.username else { throw SocialError.befriendingSelf } guard !friends.contains(username) else { throw SocialError.duplicateFriend(username: username) } friends.append(username) } } enum SocialError: Error { case befriendingSelf case duplicateFriend(username: String) } var alice = User(username: "alice") do { try alice.addFriend(username: "charlie") try alice.addFriend(username: "charlie") } catch { error } var allUsers = [ "alice": alice ] func findUser(_ username: String) -> User? { allUsers[username] } if let charlie = findUser("charlie") { print("Found \(charlie)") } else { print("charlie not found") } let dash = findUser("dash")!
-
11:01 - SocialGraph package manifest
// swift-tools-version: 6.0 import PackageDescription let package = Package( name: "SocialGraph", products: [ .library( name: "SocialGraph", targets: ["SocialGraph"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-testing.git", branch: "main"), ], targets: [ .target( name: "SocialGraph"), .testTarget( name: "SocialGraphTests", dependencies: [ "SocialGraph", .product(name: "Testing", package: "swift-testing"), ]), ] )
-
11:12 - User struct
/// Represents a user in the social graph. public struct User: Equatable, Hashable { /// The user's username, which must be unique in the service. public let username: String /// Whether or not the user should be considered visible /// when performing queries. public var isVisible: Bool /// The usernames of the user's friends. public private(set) var friends: [String] public init( username: String, isVisible: Bool = true, friends: [String] = [] ) { self.username = username self.isVisible = isVisible self.friends = friends } /// Adds a username to the user's list of friends. Throws an /// error if the username cannot be added. public mutating func addFriend(username: String) throws { guard username != self.username else { throw SocialError.befriendingSelf } guard !friends.contains(username) else { throw SocialError.alreadyFriend(username: username) } friends.append(username) } }
-
12:36 - Classes
class Pet { func speak() {} } class Cat: Pet { override func speak() { print("meow") } func purr() { print("purr") } } let pet: Pet = Cat() pet.speak() if let cat = pet as? Cat { cat.purr() }
-
12:59 - Automatic reference counting
class Pet { var toy: Toy? } class Toy {} let toy = Toy() let pets = [Pet()] // Give toy to pets for pet in pets { pet.toy = toy } // Take toy from pets for pet in pets { pet.toy = nil }
-
13:26 - Reference cycles
class Pet { weak var owner: Owner? } class Owner { var pets: [Pet] }
-
14:20 - Protocols
protocol StringIdentifiable { var identifier: String { get } } extension User: StringIdentifiable { var identifier: String { username } }
-
15:21 - Common capabilities of Collections
let string = "🥚🐣🐥🐓" for char in string { print(char) } // => "🥚" "🐣" "🐥" "🐓" print(string[string.startIndex]) // => "🥚"
-
15:31 - Collection algorithms
let numbers = [1, 4, 7, 10, 13] let numberStrings = numbers.map { number in String(number) } // => ["1", "4", "7", "10", "13"] let primeNumbers = numbers.filter { number in number.isPrime } // => [1, 7, 13] let sum = numbers.reduce(0) { partial, number in partial + number } // => 35
-
15:45 - Collection algorithms with anonymous parameters
let numbers = [1, 4, 7, 10, 13] let numberStrings = numbers.map { String($0) } // => ["1", "4", "7", "10", "13"] let primeNumbers = numbers.filter { $0.isPrime } // => [1, 7, 13] let sum = numbers.reduce(0) { $0 + $1 } // => 35
-
16:13 - Friends of friends algorithm
/// An in-memory store for users of the service. public class UserStore { var allUsers: [String: User] = [:] } extension UserStore { /// If the username maps to a User and that user is visible, /// returns the User. Returns nil otherwise. public func lookUpUser(_ username: String) -> User? { guard let user = allUsers[username], user.isVisible else { return nil } return user } /// If the username maps to a User and that user is visible, /// returns the User. Otherwise, throws an error. public func user(for username: String) throws -> User { guard let user = lookUpUser(username) else { throw SocialError.userNotFound(username: username) } return user } public func friendsOfFriends(_ username: String) throws -> [String] { let user = try user(for: username) let excluded = Set(user.friends + [username]) return user.friends .compactMap { lookUpUser($0) } // [String] -> [User] .flatMap { $0.friends } // [User] -> [String] .filter { !excluded.contains($0) } // drop excluded .uniqued() } } extension Collection where Element: Hashable { func uniqued() -> [Element] { let unique = Set(self) return Array(unique) } }
-
19:23 - async/await
/// Makes a network request to download an image. func fetchUserAvatar(for username: String) async -> Image { // ... } let avatar = await fetchUserAvatar(for: "alice")
-
19:43 - Server
import Hummingbird import SocialGraph let router = Router() extension UserStore { static let shared = UserStore.makeSampleStore() } let app = Application( router: router, configuration: .init(address: .hostname("127.0.0.1", port: 8080)) ) print("Starting server...") try await app.runService()
-
20:20 - Data race example
// Look up user let user = allUsers[username] // Store new user allUsers[username] = user // UserStore var allUsers: [String: User]
-
22:24 - Server with friendsOfFriends route
import Hummingbird import SocialGraph let router = Router() extension UserStore { static let shared = UserStore.makeSampleStore() } router.get("friendsOfFriends") { request, context -> [String] in let username = try request.queryArgument(for: "username") return try await UserStore.shared.friendsOfFriends(username) } let app = Application( router: router, configuration: .init(address: .hostname("127.0.0.1", port: 8080)) ) print("Starting server...") try await app.runService()
-
23:27 - Property wrappers
struct FriendsOfFriends: AsyncParsableCommand { @Argument var username: String mutating func run() async throws { // ... } }
-
23:57 - SocialGraph command line client
import ArgumentParser import SocialGraph @main struct SocialGraphClient: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "A utility for querying the social graph", subcommands: [ FriendsOfFriends.self, ]) } struct FriendsOfFriends: AsyncParsableCommand { @Argument(help: "The username to look up friends of friends for") var username: String func run() async throws { var request = Request(command: "friendsOfFriends", returning: [String].self) request.arguments = ["username" : username] let result = try await request.get() print(result) } }
-
26:07 - Result builders
import RegexBuilder let dollarValueRegex = Regex { // Equivalent to "\$[0-9]+\.[0-9][0-9]" "$" OneOrMore(.digit) "." Repeat(.digit, count: 2) }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.