Skip to content

Commit ce9c679

Browse files
committed
✨ Add code for Chapter 14 - Getting Started with async/await
1 parent c770828 commit ce9c679

File tree

8 files changed

+441
-0
lines changed

8 files changed

+441
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*:
2+
[<Previous](@previous) [Home](Introduction) [Next>](@next)
3+
4+
# Asynchronous Completion Handlers
5+
6+
This sample shows how to use Grand Central Dispatch and completion handlers to prepare a sandwich.
7+
8+
The steps to prepare a sandwich are:
9+
* toasting two slices of bread
10+
* slicing the ingredients (e.g. cucumbers, tomatoes) for the sandwich
11+
* spreading condiments on the bread
12+
* layering the ingredients on top of one slice of bread
13+
* putting a leaf of lettuce on top
14+
* putting another slice of bread on top
15+
16+
Some of these steps might take a while, for example toasting the bread or slicing the ingredients.
17+
18+
Completion handlers are a common way to deal with asynchronous code. They are typically implemented using (trailing) closures.
19+
This example uses `DispatchQueue` to execute long-running conde asynchronously on a background thread.
20+
*/
21+
22+
import Foundation
23+
import PlaygroundSupport
24+
25+
PlaygroundPage.current.needsIndefiniteExecution = true
26+
27+
func makeSandwich(bread: String, ingredients: [String], condiments: [String], completion: @escaping (String) -> Void) {
28+
sandwichMakerSays("Preparing your sandwich...")
29+
30+
toastBread(bread) { toasted in
31+
slice(ingredients) { sliced in
32+
sandwichMakerSays("Spreading \(condiments.joined(separator: ", and ")) om \(toasted)")
33+
sandwichMakerSays("Layering \(sliced.joined(separator: ", "))")
34+
sandwichMakerSays("Putting lettuce on top")
35+
sandwichMakerSays("Putting another slice of bread on top")
36+
37+
completion("\(ingredients.joined(separator: ", ")), \(condiments.joined(separator: ", ")) on \(toasted)")
38+
}
39+
}
40+
print("This code will be executed *before* the bread is toasted and the ingredients are sliced.")
41+
}
42+
43+
func toastBread(_ bread: String, completion: @escaping (String) -> Void) {
44+
DispatchQueue.global().async {
45+
sandwichMakerSays("Toasting the bread... Standing by...", waitFor: 5)
46+
completion("Crispy \(bread)")
47+
}
48+
}
49+
50+
func slice(_ ingredients: [String], completion: @escaping ([String]) -> Void) {
51+
DispatchQueue.global().async {
52+
let result = ingredients.map { ingredient in
53+
sandwichMakerSays("Slicing \(ingredient)", waitFor: 1)
54+
return "sliced \(ingredient)"
55+
}
56+
completion(result)
57+
}
58+
}
59+
60+
sandwichMakerSays("Hello to Cafe Complete, where we handle your order with care.")
61+
sandwichMakerSays("Please place your order.")
62+
63+
let clock = ContinuousClock()
64+
let start = clock.now
65+
makeSandwich(bread: "Rye", ingredients: ["Cucumbers", "Tomatoes"], condiments: ["Mayo", "Mustard"]) { sandwich in
66+
customerSays("Hmmm.... this looks like a delicious \(sandwich) sandwich!")
67+
68+
let time = clock.now - start
69+
print("Making this sandwich took \(time)")
70+
}
71+
72+
print("The end.") // notice how this code will be executed **before** the sandwich has been delivered to the customer!
73+
74+
//: [<Previous](@previous) [Home](Introduction) [Next>](@next)
75+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*:
2+
[<Previous](@previous) [Home](Introduction) [Next>](@next)
3+
4+
# Completion Handlers
5+
6+
This sample shows how to use completion handlers to prepare a sandwich.
7+
8+
The steps to prepare a sandwich are:
9+
* toasting two slices of bread
10+
* slicing the ingredients (e.g. cucumbers, tomatoes) for the sandwich
11+
* spreading condiments on the bread
12+
* layering the ingredients on top of one slice of bread
13+
* putting a leaf of lettuce on top
14+
* putting another slice of bread on top
15+
16+
Some of these steps might take a while, for example toasting the bread or slicing the ingredients.
17+
18+
Completion handlers are a common way to deal with asynchronous code. They are typically implemented using (trailing) closures. Note that, in this sample, we don't execute any part of the code asynchronously. The completion handlers in this sample are merely used to structure the way the individual parts of the app play together. Instead of calling a function and then receiving its return value, the code in this sample uses completion handlers to be _called back_ once the code in the called function has completed.
19+
20+
To actually make use of asynchronous capabilities, we need to make sure the code in the called functions runs asynchronously. To see an example for how this can be achieved, see [Asynchronous Completion Handlers](Asynchronous%20Completion%20Handlers).
21+
*/
22+
23+
import Foundation
24+
import PlaygroundSupport
25+
26+
PlaygroundPage.current.needsIndefiniteExecution = true
27+
28+
/// This is the main algorithm for creating a sandwich. Call it with a bread, some ingredients, and condiments of your liking.
29+
/// - Parameters:
30+
/// - bread: The type of bread you want your sandwich to be based on.
31+
/// - ingredients: A list of ingredients you want to put on your sandwich.
32+
/// - condiments: The condiments you want to be spread on the slices of bread.
33+
/// - completion: The completion handler that will be called at the end of the sandwich making process.
34+
func makeSandwich(bread: String, ingredients: [String], condiments: [String], completion: (String) -> Void) {
35+
sandwichMakerSays("Preparing your sandwich...")
36+
37+
toastBread(bread, completion: { toasted in
38+
print("\(bread) is now \(toasted)")
39+
})
40+
print("This code will be executed before the bread is toasted")
41+
42+
toastBread(bread) { toasted in
43+
print("\(bread) is now \(toasted)")
44+
}
45+
46+
toastBread(bread) { toasted in
47+
slice(ingredients) { sliced in
48+
sandwichMakerSays("Spreading \(condiments.joined(separator: ", and ")) om \(toasted)")
49+
sandwichMakerSays("Layering \(sliced.joined(separator: ", "))")
50+
sandwichMakerSays("Putting lettuce on top")
51+
sandwichMakerSays("Putting another slice of bread on top")
52+
53+
completion("\(ingredients.joined(separator: ", ")), \(condiments.joined(separator: ", ")) on \(toasted)")
54+
}
55+
}
56+
}
57+
58+
/// This function toasts your bread. Not really, but you get the point.
59+
/// - Parameters:
60+
/// - bread: The type of bread to be used.
61+
/// - completion: The completion handler that will be called at the end of the toasting process.
62+
func toastBread(_ bread: String, completion: (String) -> Void) {
63+
sandwichMakerSays("Toasting the bread... Standing by...", waitFor: 5)
64+
completion("Crispy \(bread)")
65+
}
66+
67+
/// This function slices the ingredients for your sandwich. Kind of.
68+
/// - Parameters:
69+
/// - ingredients: A list of ingredients.
70+
/// - completion: The completin handler that will be called when all ingredients have been sliced.
71+
func slice(_ ingredients: [String], completion: ([String]) -> Void) {
72+
let result = ingredients.map { ingredient in
73+
sandwichMakerSays("Slicing \(ingredient)", waitFor: 1)
74+
return "sliced \(ingredient)"
75+
}
76+
completion(result)
77+
}
78+
79+
//: The main program follows
80+
81+
sandwichMakerSays("Hello to Cafe Complete, where we handle your order with care.")
82+
sandwichMakerSays("Please place your order.")
83+
84+
// We're using a `ContinuousClock` to determine how long it took to make the sandwich.
85+
let clock = ContinuousClock()
86+
let time = clock.measure {
87+
makeSandwich(bread: "Rye", ingredients: ["Cucumbers", "Tomatoes"], condiments: ["Mayo", "Mustard"]) { sandwich in
88+
customerSays("Hmmm.... this looks like a delicious \(sandwich) sandwich!")
89+
}
90+
}
91+
92+
// This should be roughly 7 seconds (5 for toasting, and 1 for each ingredient we sliced)
93+
print("Making this sandwich took \(time)")
94+
95+
print("The end.")
96+
97+
//: [<Previous](@previous) [Home](Introduction) [Next>](@next)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*:
2+
# Getting Started with async/await
3+
Created by [Peter Friese](https://twitter.com/peterfriese) – [peterfriese.dev](https://peterfriese.dev)
4+
5+
In this Swift Playground, you will learn several ways to write asynchronous code in Swift.
6+
7+
- note: You can open and run the playground in Xcode on your Mac, but you will have a better experience when opening and running it in the Swift Playground app ([download](https://apps.apple.com/app/id1496833156) it fromn the Mac App Store).
8+
9+
This playground accompanies _Asynchronous Programming with SwiftUI and Combine_ by Peter Friese. For the best learning experience, it is recommended to follow the samples in this playground while reading chapter 14 - _Getting Started with async/await_.
10+
11+
## Table of contents
12+
* [Writing synchronous code](Synchronous%20Code)
13+
* [Using completion handlers](Completion%20Handlers)
14+
* [Writing asynchronous code with completion handlers](Asynchronous%20Completion%20Handlers)
15+
* [Using async/await to write asynchronous code](async%20await)
16+
* [Executing code in parallel using `async let`](async%20let)
17+
18+
[Next >](@next)
19+
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*:
2+
[<Previous](@previous) [Home](Introduction) [Next>](@next)
3+
4+
# Synchronous Code
5+
6+
This sample shows how to use synchronous code to prepare a sandwich.
7+
8+
The steps to prepare a sandwich are:
9+
* toasting two slices of bread
10+
* slicing the ingredients (e.g. cucumbers, tomatoes) for the sandwich
11+
* spreading condiments on the bread
12+
* layering the ingredients on top of one slice of bread
13+
* putting a leaf of lettuce on top
14+
* putting another slice of bread on top
15+
16+
Some of these steps might take a while, for example toasting the bread or slicing the ingredients. In this implementation, the sandwich maker will execute all steps serially, one after the other. This means they will stand by while waiting for the bread to be toasted (which, in this example, takes five seconds).
17+
18+
This is what we call **blocking**: no other part of the app running on the same thread will be able to execute until the respective call has completed.
19+
20+
In the other implementations, we will look at some features such as completion handlers and Swift's support for async/await to see how they can help to deal with asynchronous code.
21+
*/
22+
23+
import Foundation
24+
25+
/// This is the main algorithm for creating a sandwich. Call it with a bread, some ingredients, and condiments of your liking.
26+
/// - Parameters:
27+
/// - bread: The type of bread you want your sandwich to be based on.
28+
/// - ingredients: A list of ingredients you want to put on your sandwich.
29+
/// - condiments: The condiments you want to be spread on the slices of bread.
30+
/// - Returns: The finished sandwich.
31+
func makeSandwich(bread: String, ingredients: [String], condiments: [String]) -> String {
32+
sandwichMakerSays("Preparing your sandwich...")
33+
34+
let toasted = toastBread(bread)
35+
let sliced = slice(ingredients)
36+
37+
sandwichMakerSays("Spreading \(condiments.joined(separator: ", and ")) on \(toasted)")
38+
sandwichMakerSays("Layering \(sliced.joined(separator: ", "))")
39+
sandwichMakerSays("Putting lettuce on top")
40+
sandwichMakerSays("Putting another slice of bread on top")
41+
42+
return "\(ingredients.joined(separator: ", ")), \(condiments.joined(separator: ", ")) on \(toasted)"
43+
}
44+
45+
/// This function toasts your bread. Not really, but you get the point.
46+
/// - Parameter bread: The type of bread to be used.
47+
/// - Returns: The toasted bread.
48+
func toastBread(_ bread: String) -> String {
49+
sandwichMakerSays("Toasting the bread... Standing by...", waitFor: 5)
50+
return "Crispy \(bread)"
51+
}
52+
53+
/// This function slices the ingredients for your sandwich. Kind of.
54+
/// - Parameter ingredients: A list of ingredients.
55+
/// - Returns: A list of slices ingredients.
56+
func slice(_ ingredients: [String]) -> [String] {
57+
let result = ingredients.map { ingredient in
58+
sandwichMakerSays("Slicing \(ingredient)", waitFor: 1)
59+
return "sliced \(ingredient)"
60+
}
61+
return result
62+
}
63+
64+
//: The main program follows
65+
66+
sandwichMakerSays("Hello to Cafe Synchronous, where we execute your order serially.")
67+
sandwichMakerSays("Please place your order.")
68+
69+
// We're using a `ContinuousClock` to determine how long it took to make the sandwich.
70+
let clock = ContinuousClock()
71+
let time = clock.measure {
72+
let sandwich = makeSandwich(bread: "Rye", ingredients: ["Cucumbers", "Tomatoes"], condiments: ["Mayo", "Mustard"])
73+
customerSays("Hmmm.... this looks like a delicious \(sandwich) sandwich!")
74+
}
75+
76+
// This should be roughly 7 seconds (5 for toasting, and 1 for each ingredient we sliced)
77+
print("Making this sandwich took \(time)")
78+
79+
//: [<Previous](@previous) [Home](Introduction) [Next>](@next)
80+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*:
2+
[<Previous](@previous) [Home](Introduction) [Next>](@next)
3+
4+
# Using async/await
5+
6+
This sample shows how to use Grand Central Dispatch and completion handlers to prepare a sandwich.
7+
8+
The steps to prepare a sandwich are:
9+
* toasting two slices of bread
10+
* slicing the ingredients (e.g. cucumbers, tomatoes) for the sandwich
11+
* spreading condiments on the bread
12+
* layering the ingredients on top of one slice of bread
13+
* putting a leaf of lettuce on top
14+
* putting another slice of bread on top
15+
16+
Some of these steps might take a while, for example toasting the bread or slicing the ingredients.
17+
18+
This implementation makes use of async/await (available in Swift 5.5) to execute longer running code asynchronously.
19+
*/
20+
21+
import Foundation
22+
import PlaygroundSupport
23+
24+
PlaygroundPage.current.needsIndefiniteExecution = true
25+
26+
func makeSandwich(bread: String, ingredients: [String], condiments: [String]) async -> String {
27+
sandwichMakerSays("Preparing your sandwich...")
28+
29+
let toasted = await toastBread(bread)
30+
let sliced = await slice(ingredients)
31+
32+
sandwichMakerSays("Spreading \(condiments.joined(separator: ", and ")) om \(toasted)")
33+
sandwichMakerSays("Layering \(sliced.joined(separator: ", "))")
34+
sandwichMakerSays("Putting lettuce on top")
35+
sandwichMakerSays("Putting another slice of bread on top")
36+
37+
return "\(ingredients.joined(separator: ", ")), \(condiments.joined(separator: ", ")) on \(toasted)"
38+
}
39+
40+
func toastBread(_ bread: String) async -> String {
41+
sandwichMakerSays("Toasting the bread... Standing by...")
42+
await Task.sleep(5_000_000_000)
43+
return "Crispy \(bread)"
44+
}
45+
46+
func slice(_ ingredients: [String]) async -> [String] {
47+
var result = [String]()
48+
for ingredient in ingredients {
49+
sandwichMakerSays("Slicing \(ingredient)")
50+
await Task.sleep(1_000_000_000)
51+
result.append("sliced \(ingredient)")
52+
53+
}
54+
return result
55+
}
56+
57+
let clock = ContinuousClock()
58+
59+
sandwichMakerSays("Hello to Cafe Async, where we execute your order in asynchronously.")
60+
sandwichMakerSays("Please place your order.")
61+
62+
Task {
63+
let time = await clock.measure {
64+
let sandwich = await makeSandwich(bread: "Rye", ingredients: ["Cucumbers", "Tomatoes"], condiments: ["Mayo", "Mustard"])
65+
customerSays("Hmmm.... this looks like a delicious \(sandwich) sandwich!")
66+
print("The end.")
67+
}
68+
print("Making this sandwich took \(time)")
69+
}
70+
71+
72+
//: [<Previous](@previous) [Home](Introduction) [Next>](@next)
73+

0 commit comments

Comments
 (0)