Modern Concurrency on Apple Platforms: Using async/await with Swift 1st Edition Andrés Ibañez Kautsch pdf download
Modern Concurrency on Apple Platforms: Using async/await with Swift 1st Edition Andrés Ibañez Kautsch pdf download
or textbooks at https://ebookmass.com
https://ebookmass.com/product/modern-concurrency-on-apple-
platforms-using-async-await-with-swift-1st-edition-andres-
ibanez-kautsch/
https://ebookmass.com/product/app-development-using-ios-icloud-
incorporating-cloudkit-with-swift-in-xcode-1st-edition-shantanu-
baruah/
https://ebookmass.com/product/app-development-using-ios-icloud-
incorporating-cloudkit-with-swift-in-xcode-1st-edition-shantanu-
baruah-2/
https://ebookmass.com/product/building-with-ethereum-products-
protocols-and-platforms-1st-edition-jamie-rumbelow-2/
Building With Ethereum: Products, Protocols, and Platforms
1st Edition Jamie Rumbelow
https://ebookmass.com/product/building-with-ethereum-products-
protocols-and-platforms-1st-edition-jamie-rumbelow/
https://ebookmass.com/product/distributed-systems-concurrency-and-
consistency-1st-edition-edition-matthieu-perrin-auth/
Acknowledgments�����������������������������������������������������������������������������xiii
Preface�����������������������������������������������������������������������������������������������xv
Chapter 1: Introduction������������������������������������������������������������������������1
Important Concepts to Know���������������������������������������������������������������������������������2
Threads������������������������������������������������������������������������������������������������������������3
Concurrency and Asynchronous Programming�����������������������������������������������3
Multithreading Pitfalls�������������������������������������������������������������������������������������6
Existing Multithreading and Concurrency Tools���������������������������������������������13
Introducing async/await��������������������������������������������������������������������������������20
Requirements������������������������������������������������������������������������������������������������������21
Summary������������������������������������������������������������������������������������������������������������22
Exercises�������������������������������������������������������������������������������������������������������������23
v
Table of Contents
Chapter 3: Continuations��������������������������������������������������������������������53
Understanding Continuations������������������������������������������������������������������������������54
Converting closure-based calls into async/await������������������������������������������54
Converting delegate-based code into async/await���������������������������������������58
Supporting async/await in iOS 13 and 14�����������������������������������������������������66
Summary������������������������������������������������������������������������������������������������������������72
Exercises�������������������������������������������������������������������������������������������������������������72
vi
Table of Contents
Chapter 6: Actors������������������������������������������������������������������������������117
Introducing Actors���������������������������������������������������������������������������������������������118
Interacting with an Actor�����������������������������������������������������������������������������������120
Nonisolated Access to an Actor�������������������������������������������������������������������125
Actors and Protocol Conformance��������������������������������������������������������������������127
Actor Reentrancy����������������������������������������������������������������������������������������������129
Actors and Detached Tasks�������������������������������������������������������������������������������133
General Tips for Working with Actors����������������������������������������������������������������133
Summary����������������������������������������������������������������������������������������������������������135
Chapter 9: AsyncSequence���������������������������������������������������������������167
Introducing AsyncSequence������������������������������������������������������������������������������167
A Short Dive into Sequences and AsyncSequences������������������������������������169
AsyncSequence Concrete Types������������������������������������������������������������������170
vii
Table of Contents
AsyncSequence Example����������������������������������������������������������������������������������171
Native APIs That Use AsyncSequence���������������������������������������������������������177
The AsyncStream Object�����������������������������������������������������������������������������������177
The CoreLocationAsyncStream Project�������������������������������������������������������178
The AsyncThrowingStream Object��������������������������������������������������������������������190
Summary����������������������������������������������������������������������������������������������������������190
Index�������������������������������������������������������������������������������������������������197
viii
About the Author
Andrés Ibañez Kautsch started writing iOS
apps as a young college student in 2011. His
first introduction to concurrency programming
and its common pitfalls was in an operating
systems class that introduced the importance
(and complexity) of writing concurrent code.
Since then, he has studied how this problem
is solved in Apple’s platforms, including
iOS. Andy has worked in institutions that
make use of concurrent technologies to keep
their services running for their customers, including banks, applying the
concepts to their mobile applications.
ix
About the Technical Reviewer
Massimo Nardone has more than 22 years
of experience in security, web/mobile
development, and cloud and IT architecture.
His true IT passions are security and Android.
He has been programming and teaching
how to program with Android, Perl, PHP, Java,
VB, Python, C/C++, and MySQL for more than
20 years.
He holds a Master of Science degree in
Computing Science from the University of
Salerno, Italy.
He has worked as a project manager, software engineer, research
engineer, chief security architect, information security manager,
PCI/SCADA auditor, and senior lead IT security/cloud/SCADA architect
for many years.
xi
Acknowledgments
Whenever you decide to buy a technical book, you usually see one or two
author names, and maybe a reviewer attached to it, but there are a lot of
people involved in a single book, both directly and indirectly. It will not
be possible to acknowledge everyone who has helped make this book
possible, but I’d like to single out a few.
First, I’d like to give a big kudos to Alexis Marechal. Alexis has, in
short, saved my career as a software developer. Alexis is not only a great
teacher, but also a great friend. His teaching style has greatly inspired
mine, and I hope I can do justice to that in this book. A big thank-you
to Carlos Anibarro and Jose Luis Vera for their teachings and words of
encouragement as well.
Writing a book is something I have always wanted to do, but Ernesto
Campohermoso is the first person who ever asked me, “When are you
going to write a book?” That single question was a great push for this book
to come into existence.
Ivan Galarza is the first person I mentored in iOS development, and he
became a very reliable iOS engineer in a short time thanks to all the talent
he has. He was the ideal candidate to review a book in progress. His input
helped me make sure everything makes sense and that this book will be
something people would like to read.
A big thank-you to all the folks at Apress for the process that made
writing this book possible. The entire experience has been a joy, from
being contacted to write a book to turning in the final chapters. The final
form of this book would have not been possible without their tenacity and
amazing work ethic.
xiii
Acknowledgments
Finally, I’d like to give a big thanks to all the readers of my blog,
andyibanez.com. The comments and feedback I received in the original
tutorial series for this topic helped me greatly improve this book. I cannot
reply to every message I get, but I am thankful to all my readers. Thank
you all.
xiv
Preface
Concurrency is a topic a lot of developers find scary. And for good reason.
Concurrency is probably one of the most complicated topics in the world
of programming. When you look at it from a very high level, concurrency
allows us to do a lot of work at the same time. Sometimes related,
sometimes unrelated. The definition is simple, but as a programmer, it is
hard to reason about concurrent programs. We learn to write code that
can be read from top to bottom, and code usually makes sense when you
read it in such a manner. In fact, the way developers reason about code
is not too different to how non-programmers reason about a cooking
recipe. Humans can understand anything they read if it is structured and
makes sense.
But the very nature of concurrency breaks this top-to-bottom reading
flow. Not only do we need to reason about concurrency differently, but we
also need to keep in mind other factors that make it harder, such as shared
mutable data, locks, threads…. Concurrency is naturally very complicated,
especially when dealing with lower-level tools.
Luckily, the new async/await system, introduced in 2021, makes
concurrency easier to reason about because it abstracts a lot of the
complexity behind language-integrated features that are easy to use and
hard to misuse. If you have written concurrent code before with anything
other than async/await in any other platform (and that includes but is
not limited to iOS, macOS, and other Apple platforms), you do not have
to concern yourself with mutexes, semaphores, or any other low-level
concurrency primitives. If you have never written concurrent code before,
you can write amazing multithreaded software in a way that makes
sense without ever having to concern yourself with the complexities of
xv
Preface
traditional concurrency. This new system is very powerful and easy to use
for both developers familiar with concurrency as well as those who have
never written concurrent code before.
xvi
Preface
xvii
Preface
xviii
CHAPTER 1
Introduction
Programmers are used to writing programs that are executed in a linear
fashion. As you write, test, and execute your program, you expect your
instructions to run in the order that you wrote them. In Listing 1-1, you
have a program that will first assign a variable a to the number 2. It will
then assign a variable b to the number 3, followed by assigning a variable
sum, the sum of a + b, and it will finally print a result to the console. There
is no way this program will work if you try to print(sum) before you even
managed to calculate the value for sum.
let a = 2
let b = 3
let sum = a + b // This variable depends on the values for
a and b, but the variables themselves can be assigned in
any order.
print(sum) // We can only print a variable that we have the
value of.
contents, but the execution of these lines will also be done procedurally in
the same order they were written until control flow is returned to the caller.
Even people who are not programmers can follow any instruction set if
they are written in a specific order and if they are doing one thing at a time.
Someone following a cooking recipe, or someone building a doghouse
from an online tutorial may not be a programmer, but people are naturally
good at doing something if they have the steps clearly laid down.
But computer programs grow and become more complex. While it is
true that a lot of complex software can be written that follows such a linear
execution flow, often programs will need to start doing more than one
thing at once; rather than having a clear code execution path that you can
follow with your bare eyes, your program may need to execute in such a
way that it’s not obvious to tell what’s going on at a simple glance of the
source code. Such programs are multithreaded, and they can run multiple
(and often – but not always – unrelated) code paths at the same time.
In this book, we will learn how to use Apple’s async/await
implementation for asynchronous and multithreaded programming. In
this chapter, we will also talk about older technologies Apple provides for
this purpose, and how the new async/await system is better and helps you
to not concern yourself with traditional concurrency pitfalls.
2
Chapter 1 Introduction
Threads
The concept of Thread can vary even when talked about in the same
context (in this case, concurrency, and asynchronous programming).
In this book, we will treat a thread as a unit of work that can run
independently. In iOS, the Main Thread runs the UI of your app, so every
UI-related task (updating the view hierarchy, removing and adding views)
must take place in the main thread. Attempting to update the UI outside of
the main thread can result in unwanted behavior or, even worse, crashes.
In low-level multithreading frameworks, multithreading developers
will manually create threads and they need to manually synchronize
them, stop them, and do other thread management operations on their
own. Manually handling threads is one of the hardest parts of dealing with
multithreading in software.
3
Chapter 1 Introduction
There are different APIs throughout Apple’s SDKs that make use of
concurrency. Listing 1-2 shows how to request permission to use Touch ID
or Face ID, depending on the device.
func requestBiometricUnlock() {
let context = LAContext()
if canEvaluate {
if context.biometryType != .none {
// (1)
context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "To access your data") {
(success, error) in
// (2)
if success {
// ...
}
}
}
}
}
4
Chapter 1 Introduction
thread your app was running on may be doing something entirely different
and not even related to your app while the system is running context.
evaluatePolicy. When the user responds to the prompt, either accepting
or rejecting the biometric request, it will deliver the result to your app. The
system will wait for an appropriate time to notify your app with the user’s
selection. The selection will be delivered to your app in the completion
handler (also called a callback) on (2), at which point your app will be in
control of the thread again. The selection may be delivered in a different
thread than the one which launched the context.evaluatePolicy call –
this is important to know, because if the response updates the UI, you
need to do that work on the main thread. This is also called a blocking
mechanism or interruption, as evaluatePolicy is a blocking call for the
thread. If you have done iOS for at least a few months now, you are familiar
with this way of dealing with various events. URLSession, image pickers,
and more APIs make use of this mechanism.
People often think that asynchronous programming is the act
of running multiple tasks at once. This is a different concept called
Multithreading, and we will talk about it in the next point.
5
Chapter 1 Introduction
Multithreading Pitfalls
Concurrency and multithreading are traditionally hard problems to solve.
Ever since their introduction in the computer world, developers have had
to develop paradigms and tools to make dealing with concurrency easier.
Because programmers are used to thinking procedurally, writing code that
executes at unexpected times is hard to get right.
In this section we will talk about some of the problems developers
who write low-level multithreading code often face, and the models
they have created to work with them. It is important you understand this
section because these traditional problems are real, but their solutions
are already implemented in the async/await system. It will also help you
decide which technology you should use next time you need to implement
concurrency or multithreading in your programs.
Deadlocks
In the context of multithreading, a deadlock occurs when two different
processes are waiting on the other to finish, effectively making it
impossible for any of them to finish to begin with.
This can happen when both processes are sharing a resource. If
Thread B wants a resource that Thread A is holding, and Thread A wants a
resource that Thread B has, both processes will be waiting for each other to
finish, sitting in a perpetual deadlock state. Figure 1-1 illustrates how this
might occur.
6
Chapter 1 Introduction
Figure 1-1 illustrates how Thread A may try to access Resource C and
Resource D, one after the other, and how Thread B may try to do the same
but in different order. In this example, the deadlock will happen rather
soon, because Thread A will get hold of Resource C while Thread B gets
hold of Resource D. Whoever needs each other’s resource first will cause
the deadlock.
Despite how simple this problem looks, it is the culprit of a lot of bugs
in a lot of multithreaded software. The simple solution would be to prevent
each process from attempting to access a busy resource. But how can this
be achieved?
7
Chapter 1 Introduction
Mutex
Mutex is short for Mutually exclusive lock (or flag). A mutex will signal
other processes that a process is currently using some resource by adding
a lock to it and preventing other processes from grabbing until that lock is
freed. Ideally, a process will acquire a lock to all the resources it will need
at once, even before using them. This way, if Thread A needs Resource C
and Resource D, it can lock them before Thread B tries to access them.
Thread B will wait until all the locks are freed before attempting to access
the resources itself. Figure 1-2 illustrates how this is done.
Semaphores
9
Chapter 1 Introduction
Starvation
The Starvation problem happens when a program is stuck in a state of
perpetually waiting for resources to do some work but never receiving
them. Figure 1-3 illustrates this problem.
10
Chapter 1 Introduction
philosophers get to eat, and only the ones who are not sitting directly next
to each other. When a philosopher is not eating, he puts both forks down.
But there can be a situation in which a philosopher decides to overeat,
leaving another to starve.
Luckily for low-level multithreading developers, it’s possible to use
the semaphores we talked about above to solve this problem. The idea is
to have a semaphore that keeps track of the used forks, so it can signal
another philosopher when a fork is free to be used.
Race Conditions
Likely the multithreading problem most developers are familiar with,
Race Conditions are very similar to deadlocks, with the exception
that they can cause data or memory corruption that will lead to other
unpleasant consequences. When we are dealing with multithreading,
it isn’t inherently a bad thing that two processes are reading data at the
same time. If the resource is read-only, there is no harm. However, if the
processes can modify or update the resource in any way, the processes
will continually overwrite the data another process just wrote, and this
will eventually lead to reading and writing corrupted data. If the resource
in question is user data, we will have unhappy users. If the resource is
provided by the OS, we can cause other unwanted consequences and
eventually reach one of the multithreading issues such as deadlocks.
Figure 1-4 illustrates this pitfall.
11
Chapter 1 Introduction
Figure 1-4. Multiple threads writing to the same file at the same time
12
Chapter 1 Introduction
Livelocks
There is a saying in life that goes “you can have too much of a nice thing.”
It is important to understand that throwing locks willy-nilly is not a
solution to all multithreading problems. It may be tempting to identify a
multithreading issue, throw a mutex at it, and call it a day. Unfortunately,
it’s not so easy. You need to understand your problem and identify where
the lock should go.
Similarly, this problem can occur when processes are too lenient
with the conditions under which they can release and attain resources. If
Thread A can ask Thread B for a resource and Thread B complies without
any thought, and Thread B can do the same to Thread A, it may happen
that eventually they will not be able to ask each other for the resource back
because they can’t even get to that point of the execution.
This is a classic example in real life. Suppose you are walking towards a
closed door and someone else arrives to it at almost the same time as you.
You may want to let the other person go in first, but they may tell you that
you should go first instead. If you are familiar with this awkward feeling,
you know what a livelock feels like for our multithreaded programs.
13
Chapter 1 Introduction
pthreads
pthreads (POSIX Threads) are the implementation of a standard defined
by the IEEE.1 The POSIX part of their name (Portable Operating System
Interface) tells us that they are available in many platforms, and not only
on Apple’s operating systems. Traditionally, hardware vendors used to sell
their products offering their own, proprietary multithreading APIs. Thanks
to this standard you can expect to have a familiar interface in multiple
POSIX systems. The great advantage of pthreads is that they are available
in a wide array of systems if they are POSIX compliant, including some
Linux distributions.
The disadvantage is that pthreads are purely written in C. C is a very
low-level programming language, and it is a language that is slowly fading
from the knowledge base of many developers. The younger they are, the
less likely they are to know C. While I do not expect C to disappear any
time soon, the truth is that it’s very hard to find iOS developers who know
the C programming language, let alone use it correctly. pthreads are the
lowest-level multithreading APIs we have available, and as such they
have a very steep learning curve. If you opt to use pthreads, it’s because
you have highly specific needs to control the entire multithreading and
concurrency flow, though most developers will not need to drop down to
these levels often, if at all. You will be launching threads and managing
resources such as mutex more manually. If you are familiar with pthreads
because you worked with them in another platform, you can use your
knowledge here too, but be aware future programmers who will maintain
your code may not be familiar with either pthreads or the C language itself.
1
IEEE POSIX 1003.1c standard (1995) – https://standards.ieee.org/
ieee/1003.1c/1393/
14
Chapter 1 Introduction
NSThreads
NSThread is a Foundation object provided by Apple. They are low level,
but not as low level as pthreads. Since they are foundation objects, they
offer an Objective-C interface. Offering this interface will expose the tool to
many more developers, but as time goes on, less iOS developers are likely
to know Objective-C. In fact, it’s entirely possible to find iOS developers
with some years of experience who have never had to use this language
before, although it can be used in Swift as well.
If you want to work with multithreading, you will end up creating
multiple NSThread objects, and you are responsible for managing them
all. Each NSThread object has properties you can use to check the status of
the thread, such as executing, cancelled, and finished. You can set the
priority of each thread which is a very real use case in multithreading. You
can even get the main thread back and ask if the current thread is the main
thread to avoid doing expensive work on it.
Just like pthreads, most developers will not have to drop down to this
level often, if at all.
15
Chapter 1 Introduction
DispatchQueue.main.async {
nameLabel.text = userResponse.username
}
Looks familiar? This is one of the most famous calls on the GCD
because it allows you to defer some work quickly and painlessly to be
done on the main thread. This call is likely to be found after a subclass of
URLSessionTask finishes some work and you want to update your UI, for
example.
The GCD, being high level, saves you from doing a lot of work than
pthreads and NSThreads. With the GCD, you will never concern yourself
with managing threads manually. It truly is a high-level framework in that
sense, but it carries its own baggage as well. Listing 1-4 shows a classic
issue some developers have when working with the GCD.
func fetchUser() {
userApi.fetchUserData { userData in
DispatchQueue.main.async {
self.usernameLabel.text = userData.username
self.userApi.fetchFavoriteMovies(for: userData.id)
{ movies in
DispatchQueue.main.async {
self.userMovies = movies
}
}
}
}
}
16
Chapter 1 Introduction
Listing 1-4 shows hypothetical code that would retrieve a user’s data
and their favorite movies from somewhere. This usage is very typical with
the GCD. When your work requires you to do more than one call that
depends on the task of another, your code starts looking like a pyramid
of doom. There are ways to solve this (like creating different functions for
fetching the user data and their movies), but it’s not entirely elegant.
Despite its drawbacks, the GCD is a very popular way to create
concurrent and multithreaded code. It has a lot of protections in place
already for you, so you don’t have to be an expert in the theory of
multithreading to avoid making mistakes. The samples we saw here only
show a very tiny bit of functionality it offers. While the tool is high level
enough to save you from many headaches, it exposes a lot of lower level
functionality, and it gives you the ability to directly interact with some
primitives such as semaphores.
Finally, this technology is open source, so it’s possible to find in
platform outside of anything Apple develops. The GCD is big, and talking
about its features would go outside the scope of this book, but be aware
that it has been in use for a very long time and it’s possible you are going to
see some advanced techniques with it in your career.
17
Chapter 1 Introduction
task in the main thread if it sees it’s not worth it to launch another thread
for its work. Listing 1-5 creates two different tasks that count from 1 to 10
and from 10 to 20 in different queues.
/// You can give your queue an optional name, if you need
to identify it later.
operationQueue.name = "Counting queue"
/// To ensure the program doesn't exit early while the
operations are running.
operationQueue.waitUntilAllOperationsAreFinished()
}
18
Chapter 1 Introduction
It’s worth to note that while you don’t have thread management
abilities with this framework, you can check the status of each operation
(isCancelled, isFinished, etc.), and you can cancel your operations at
any time (by calling cancel on it). If an operation gets cancelled, other
operations that depended on it will also be cancelled.
19
Chapter 1 Introduction
Introducing async/await
async/await is a high-level system to write concurrent and multithreaded
code. By using it, you don’t have to think about manual thread
management or deadlocks. The system will take care of all those details
and more for you under the hood. By using very simple constructs, you
will be able to write safe and powerful concurrent and multithreaded
code. It is worth noting that this system is very high level. It’s hard to use
it incorrectly. While the system won’t give you fine-grain control over the
concurrency and multithreading primitives, it will give you a whole set of
abstractions to think of multithreaded code very differently. Semantically,
it’s hard to compare async/await with any of the other systems we have
explored before, because async/await is deeply integrated into Swift, the
language, itself. It’s not an add-on in the form of a framework or a library.
The primitives this system exposes are easier to understand thanks to
Swift’s focus on readability.
To give you a quick look of what we will be studying for the rest of this
book, we will rewrite Listing 1-4 in Listing 1-7 using async/await.
@MainActor
func fetchUser() async {
let userData = await userApi.fetchUserData()
usernameLabel.text = userData.username
let movies = userApi.fetchMovies(for: userData.id)
userMovies = movies
}
20
Chapter 1 Introduction
Listing 1-7 has gotten rid of a lot of code in Listing 1-4. You can see
that the code that defers the results to the main thread is gone. You can
also see that the code can be read from top to bottom – just like a normal
procedural programming style would do! This version of the user data
fetching API is simpler to read and simpler to write. @MainActor is what’s
known as a global actor. We will explore actors and global actors later in the
book. For now, know that the @MainActor will ensure that member updates
within a function or class marked with it will run in the main thread.
By understanding when to use the system’s keywords directly (Listing 1-7
shows that async and await are keywords themselves) and the rest of the
system’s primitives, you will be able to write concurrent and multithreaded
code that any programmer will be able to understand. And to make things
even better, if a developer is new to Apple platform development but they
have experience with another technology that makes use of async/await,
they will be able to quickly become familiar with the system. Your codebase
will be cleaner and welcoming for new developers in your team.
Using async/await will prevent you from having to write low-level
concurrent code. You will never manage threads directly. Locks are also
not your responsibility at all. As you saw, this new system allows us to write
expressive code, and pyramids of doom are a thing of the past.
It is important to remember that as much as a high-level system this is,
you may find the extremely peculiar case in which you need a lower-level
tool. That said, most developers will be able to go through their careers
without finding a single use case to put async/await aside.
Requirements
To follow along, you will need to download at least Xcode 13 from the App
Store. If you want to use async/await in iOS 14 and iOS 13, you will need
Xcode 13.3. You should be comfortable reading and writing Swift code.
Some examples will be written in SwiftUI so as to prevent the UI code
distracting you from the actual contents of each chapter.
21
Chapter 1 Introduction
Summary
Concurrency and multithreading are traditional computing problems.
Doing concurrency at the lower level and managing resources yourself can
be a challenge because they are easy to misuse. Misusing these resources
and not getting your multithreaded code right will result in bugs in the
best case and user data corruption in the worst case. Because of this, many
developers, including Apple, have designed multiple tools to abstract
away the details and provide easier interfaces. In the case of Apple, they
have given developers the following tools (sorted from lower level to
higher level):
• pthreads
• NSThreads
• async/await
22
Chapter 1 Introduction
Exercises
ANSWER THE QUESTIONS
23
CHAPTER 2
Introducing
async/await
In the last chapter, we talked about some common problems people run
into when writing concurrent and multithreaded code. We also saw some
of the options Apple has for developers for this job, from lower-level
alternatives that are hard to use correctly, to higher-level alternatives that
are easier to use, but have less flexibility.
The rest of this book will focus on the new concurrency system which
we will simply call async/await. In this chapter, we will explore the basic
building of this new system – the async and await keywords. We will not
delve into multithreading just yet. Once you understand how to use these
keywords and how they behave with the APIs in the SDK you already know,
you will have the right tools to learn everything this new system can do. But
before we move on, let’s talk a bit more about closures and callbacks (or
blocks, as they are called in Objective-C).
And the second screen, Figure 2-2, shows a profile view once
successfully authenticated:
26
Chapter 2 Introducing async/await
Upon authenticating, the app will consume two different web services:
One to retrieve the user profile, and one to fetch their followers and
following users counts.
Note If you run this app in the simulator, you will need to enroll to
Face ID. To do this, open the simulator, go to Feature menu ➤ FaceID,
and ensure Enrolled is ticked. Rerun the app afterwards.
27
Chapter 2 Introducing async/await
class UserAPI {
func fetchUserInfo(
completionHandler: @escaping (_ userInfo: UserInfo?,
_ error: Error?) -> Void
) {
let url = URL(string: "https://www.andyibanez.com/
fairesepages.github.io/books/async-await/user_
profile.json")!
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { data,
response, error in
if let error = error {
completionHandler(nil, error)
} else if let data = data {
do {
let userInfo = try JSONDecoder().
decode(UserInfo.self, from: data)
completionHandler(userInfo, nil)
} catch {
completionHandler(nil, error)
}
}
}
dataTask.resume()
}
//...
}
28
Chapter 2 Introducing async/await
Note To keep the book short, we will take some shortcuts such as
force-unwrapping optionals. Remember to always work with your
optionals safely and responsibly.
29
Chapter 2 Introducing async/await
Now, we want to download this user info and update a variable with it.
Open the file UserProfileViewModel.swift and find the fetchUserInfo()
function. Listing 2-3 shows what the call site looks like.
The code above will call the fetchUserInfo(_) function from our API
object. It will set a userInfo variable from our view model if it succeeds
and set an error if otherwise.
Run the app and check the print statements.
New developers may expect the console to print:
30
Other documents randomly have
different content
are no branchiostegals and a median series of plates is present on
the head. The body is armed with five rows of large bony bucklers,—
each often with a hooked spine, sharpest in the young. Besides
these, rhombic plates are developed on the tail, besides large fulcra.
The sturgeons are the youngest of the Ganoids, not occurring before
the Lower Eocene, one species, Acipenser toliapicus occurring in the
London clay. About thirty living species of sturgeon are known,
referred to three genera: Acipenser, found throughout the Northern
Hemisphere, Scaphirhynchus, in the Mississippi Valley, and Kessleria
(later called Pseudoscaphirhynchus), in Central Asia alone. Most of
the species belong to the genus Acipenser, which abounds in all the
rivers and seas in which salmon are found. Some of the smaller
species spend their lives in the rivers, ascending smaller streams to
spawn. Other sturgeons are marine, ascending fresh waters only for
a moderate distance in the spawning season. They range in length
from 2½ to 30 feet.
All are used as food, although the flesh is rather coarse and beefy.
From their large size and abundance they possess great economic
value. The eggs of some species are prepared as caviar.
In the genus Acipenser the snout is sharp and conical, and the
shark-like spiracle is still retained.
Fig. 8.—Shovel-nosed Sturgeon. Scaphirhynchus platyrhynchus
(Rafinesque). Ohio River.
The Macrosemiidæ are elongate fishes with long dorsal fin, the
numerous species being found in the Triassic, Jurassic, and
Cretaceous of Europe. Macrosemius rostratus has a very high,
continuous dorsal. Macropistius arenatus is found in the Cretaceous
of Texas, the only American species known. Prominent European
genera are Notagogus, Ophiopsis, and Petalopteryx.
Intermediate between the allies of the gars and the modern herrings
is the large extinct family of Pholidophoridæ, referred by Woodward
to the Isospondyli, and by Eastman to the Lepidostei. These are
small fishes, fusiform in shape, chiefly of the Triassic and Jurassic.
The fins are fringed with fulcra, the scales are ganoid and rhombic,
and the vertebræ reduced to rings. The mouth is large, with small
teeth, and formed as in the Isospondyli. The caudal is scarcely
heterocercal.
Fig. 19.—Caturus elongatus Agassiz. Jurassic. Family Isopholidæ.
(After Zittel.)
The genus Megalurus differs from Amia in the still shorter dorsal fin,
less than one-third the length of the back. The body is elongate and
much depressed. Megalurus lepidotus and several other species are
found in the lithographic stones of Bavaria and elsewhere.
The Oligopleuridæ.—In the extinct family Oligopleuridæ the scales
are cycloid, the bones of the head scarcely enameled, and the
vertebræ well ossified. Fulcra are present, and the mouth is large,
with small teeth. The genera are Oligopleurus, Ionoscopus, and
Spathiurus, the species not very numerous and chiefly of the
Cretaceous. Ionoscopus cyprinoides of the lithographic shales of
Bavaria is a characteristic species.
From the three families last named, with the Pholidophoridæ, there
is an almost perfect transition from the Ganoid fishes to teleosteans
of the order of Isospondyli, the primitive order from which all other
bony fishes are perhaps descended. The family of Leptolepidæ,
differing from Oligopleuridæ in the absence of fulcra, is here placed
with the Isospondyli, but it might about as well be regarded as
Ganoid.
CHAPTER III
ISOSPONDYLI
ebookmasss.com