100% found this document useful (1 vote)
23 views

Modern Concurrency on Apple Platforms: Using async/await with Swift 1st Edition Andrés Ibañez Kautsch pdf download

The document provides links to various ebooks and textbooks available for download at ebookmass.com, including titles on modern concurrency in Apple platforms, asynchronous programming with SwiftUI, and app development using iOS iCloud. Each book is authored by different experts and covers specific technical topics relevant to software development. Additionally, it includes information about the author and technical reviewer of the featured book on concurrency.

Uploaded by

jedzkesley
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
23 views

Modern Concurrency on Apple Platforms: Using async/await with Swift 1st Edition Andrés Ibañez Kautsch pdf download

The document provides links to various ebooks and textbooks available for download at ebookmass.com, including titles on modern concurrency in Apple platforms, asynchronous programming with SwiftUI, and app development using iOS iCloud. Each book is authored by different experts and covers specific technical topics relevant to software development. Additionally, it includes information about the author and technical reviewer of the featured book on concurrency.

Uploaded by

jedzkesley
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 76

Download the full version and explore a variety of ebooks

or textbooks at https://ebookmass.com

Modern Concurrency on Apple Platforms: Using


async/await with Swift 1st Edition Andrés Ibañez
Kautsch

_____ Tap the link below to start your download _____

https://ebookmass.com/product/modern-concurrency-on-apple-
platforms-using-async-await-with-swift-1st-edition-andres-
ibanez-kautsch/

Find ebooks or textbooks at ebookmass.com today!


We believe these products will be a great fit for you. Click
the link to download now, or visit ebookmass.com
to discover even more!

Asynchronous Programming with SwiftUI and Combine:


Functional Programming to Build UIs on Apple Platforms 1st
Edition Peter Friese
https://ebookmass.com/product/asynchronous-programming-with-swiftui-
and-combine-functional-programming-to-build-uis-on-apple-
platforms-1st-edition-peter-friese/

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/

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/

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-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/

Observability with Grafana: Monitor, control, and


visualize your Kubernetes and cloud platforms using the
LGTM stack Rob Chapman
https://ebookmass.com/product/observability-with-grafana-monitor-
control-and-visualize-your-kubernetes-and-cloud-platforms-using-the-
lgtm-stack-rob-chapman/

Distributed Systems. Concurrency and Consistency 1st


Edition Edition Matthieu Perrin (Auth.)

https://ebookmass.com/product/distributed-systems-concurrency-and-
consistency-1st-edition-edition-matthieu-perrin-auth/

High-Performance Web Apps with FastAPI: The Asynchronous


Web Framework Based on Modern Python 1st Edition Malhar
Lathkar
https://ebookmass.com/product/high-performance-web-apps-with-fastapi-
the-asynchronous-web-framework-based-on-modern-python-1st-edition-
malhar-lathkar/

High-Performance Web Apps with FastAPI: The Asynchronous


Web Framework Based on Modern Python 1st Edition Malhar
Lathkar
https://ebookmass.com/product/high-performance-web-apps-with-fastapi-
the-asynchronous-web-framework-based-on-modern-python-1st-edition-
malhar-lathkar-2/
Modern
Concurrency on
Apple Platforms
Using async/await with Swift

Andrés Ibañez Kautsch
Modern Concurrency
on Apple Platforms
Using async/await with Swift

Andrés Ibañez Kautsch


Modern Concurrency on Apple Platforms: Using async/await with Swift

Andrés Ibañez Kautsch


La Paz, Bolivia

ISBN-13 (pbk): 978-1-4842-8694-4 ISBN-13 (electronic): 978-1-4842-8695-1


https://doi.org/10.1007/978-1-4842-8695-1

Copyright © 2023 by Andrés Ibañez Kautsch


This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or
part of the material is concerned, specifically the rights of translation, reprinting, reuse of
illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way,
and transmission or information storage and retrieval, electronic adaptation, computer software,
or by similar or dissimilar methodology now known or hereafter developed.
Trademarked names, logos, and images may appear in this book. Rather than use a trademark
symbol with every occurrence of a trademarked name, logo, or image we use the names, logos,
and images only in an editorial fashion and to the benefit of the trademark owner, with no
intention of infringement of the trademark.
The use in this publication of trade names, trademarks, service marks, and similar terms, even if
they are not identified as such, is not to be taken as an expression of opinion as to whether or not
they are subject to proprietary rights.
While the advice and information in this book are believed to be true and accurate at the date of
publication, neither the authors nor the editors nor the publisher can accept any legal
responsibility for any errors or omissions that may be made. The publisher makes no warranty,
express or implied, with respect to the material contained herein.
Managing Director, Apress Media LLC: Welmoed Spahr
Acquisitions Editor: Mark Powers
Development Editor: James Markham
Coordinating Editor: Jessica Vakili
Distributed to the book trade worldwide by Springer Science+Business Media New York, 1
New York Plaza, Suite 4600, New York, NY 10004-1562, USA. Phone 1-800-SPRINGER, fax (201)
348-4505, e-mail orders-ny@springer-sbm.com, or visit www.springeronline.com. Apress Media,
LLC is a California LLC and the sole member (owner) is Springer Science + Business Media
Finance Inc (SSBM Finance Inc). SSBM Finance Inc is a Delaware corporation.
For information on translations, please e-mail booktranslations@springernature.com; for
reprint, paperback, or audio rights, please e-mail bookpermissions@springernature.com.
Apress titles may be purchased in bulk for academic, corporate, or promotional use. eBook
versions and licenses are also available for most titles. For more information, reference our Print
and eBook Bulk Sales web page at http://www.apress.com/bulk-sales.
Any source code or other supplementary material referenced by the author in this book is
available to readers on the GitHub repository: https://github.com/Apress/Modern-Concurrency-
on-Apple-Platforms. For more detailed information, please visit http://www.apress.com/
source-code.
Printed on acid-free paper
To my mother Renata and to my brother Gastón, for all the
support and patience they have always shown and for
everything they have always done for me.
Table of Contents
About the Author���������������������������������������������������������������������������������ix

About the Technical Reviewer�������������������������������������������������������������xi

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

Chapter 2: Introducing async/await���������������������������������������������������25


Closures and Their Place in Concurrency and Multithreading����������������������������25
Getting Started with async/await������������������������������������������������������������������������34
The async keyword����������������������������������������������������������������������������������������35
The await keyword����������������������������������������������������������������������������������������37
Using async/await�����������������������������������������������������������������������������������������39

v
Table of Contents

async get properties��������������������������������������������������������������������������������������49


async/await in iOS 13 and iOS 14�����������������������������������������������������������������50
Summary������������������������������������������������������������������������������������������������������������50
Exercises�������������������������������������������������������������������������������������������������������������51

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

Chapter 4: Structured Concurrency����������������������������������������������������73


Understanding Structured Concurrency��������������������������������������������������������������75
The async let construct���������������������������������������������������������������������������������75
Task Groups���������������������������������������������������������������������������������������������������78
Summary������������������������������������������������������������������������������������������������������������86
Exercises�������������������������������������������������������������������������������������������������������������86

Chapter 5: Unstructured Concurrency������������������������������������������������89


Tasks in Depth�����������������������������������������������������������������������������������������������������89
Creating Tasks�����������������������������������������������������������������������������������������������90
Unstructured Concurrency in Action��������������������������������������������������������������93
The Task Tree����������������������������������������������������������������������������������������������������100
Error Propagation����������������������������������������������������������������������������������������103
Task Cancellation����������������������������������������������������������������������������������������103
Task Cancellation and Task Groups�������������������������������������������������������������111

vi
Table of Contents

Unstructured Concurrency with Detached Tasks����������������������������������������������112


Summary����������������������������������������������������������������������������������������������������������113
Exercises�����������������������������������������������������������������������������������������������������������114

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 7: Sendable Types���������������������������������������������������������������137


Understanding Sendable Types�������������������������������������������������������������������������137
The Sendable Protocol���������������������������������������������������������������������������������139
Summary����������������������������������������������������������������������������������������������������������149

Chapter 8: The Main Actor & Final Actors����������������������������������������151


The Main Thread�����������������������������������������������������������������������������������������������151
The Main Actor��������������������������������������������������������������������������������������������153
Understanding Global Actors�����������������������������������������������������������������������������160
Creating Global Actors���������������������������������������������������������������������������������160
Summary����������������������������������������������������������������������������������������������������������165

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

Chapter 10: @TaskLocal�������������������������������������������������������������������191


Introducing the @TaskLocal Property Wrapper�������������������������������������������������192
Using @TaskLocal���������������������������������������������������������������������������������������192
Summary����������������������������������������������������������������������������������������������������������196

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.

Who This Book Is For


This book is aimed at intermediate iOS developers and above. You should
find this book to be of your skill level if you have been writing Apple apps
a bit over a year. Having experience with other concurrency tools in either
Apple platforms or anything else may help you grasp this book easier, but
previous concurrency knowledge is by no means necessary. You should be
familiar with the basic process of writing and maintaining an iOS app to
take advantage of this book.

How This Book Is Organized


I tried my best to organize this book in a way that makes sense. There were
topics that recursively required the knowledge of other topics before they
could be properly understood. For those situations, I spent a little bit more
time explaining some concepts at a higher level so you could get by before
they got properly introduced.
Chapter 1 introduces concurrency and its traditional problems when
trying to implement it. It discusses low-level concurrency primitives and
how they can be used. It also discusses the traditional problems you will
find when you try to implement a concurrency system without using
higher-level tools such as async/await.
Chapter 2 formally introduces “async” and “await” as keywords of the
Swift language. These two keywords are essential to understand to use the
concurrency system effectively. Every single topic makes use of async/
await, so this chapter is completely dedicated to these keywords.

xvi
Preface

Chapter 3 introduces Continuations, a tool that helps you migrate


closure-based or even delegate-based code to use async/await. This
can help you “bridge” such code into the async/await world, making
them easier to write and understand. You will also learn how to backport
concurrent code to iOS 14 and 13.
Chapter 4 introduces the concept of Structured Concurrency. You will
write your first concurrent code here. Structured concurrency helps you
write multithreaded code that is easy to read and write.
Chapter 5 introduces the concept of Unstructured Concurrency, a
topic that will help you write streamlined concurrent code with a little bit
more of flexibility than Structured Concurrency.
Chapter 6 introduces the concept of Actors. Actors are reference types
that isolate their own state, so they are useful when you need to write
concurrent code that deals with shared mutable state. It helps you answer
questions such as “What happens if two processes write to this variable at
the same time?”
Chapter 7 is all about Sendable types, which are objects that can
be used safely in concurrent code, either because they have built-in
protection (such as actors) or because the developers took special care of
these types to make them usable concurrently (like classes that implement
their own synchronization mechanism).
Chapter 8 discusses Global Actors, a tool to help you write concurrent
code that is spread out across different files and even frameworks. It also
discusses the Main Actor, a global actor that you use when you need to
update your app’s UI.
Chapter 9 is all about async sequences. These sequences can help
you receive values over time in an asynchronous context, helping you
eliminate the usage of closures and delegates in some scenarios.
Chapter 10, the final chapter, covers the usage of a property wrapper
called @TaskLocal, which you can use to share data down a concurrent
tasks tree.

xvii
Preface

Before You Get Started


While Apple managed to backport the new concurrency system to iOS 13
and iOS 14, it is recommended you study this system with iOS 15. There
are no native APIs that use async/await in lower iOS versions, and you
would need to provide an alternative to them every time you are interested
in using them.
It is recommended you have at least Xcode 13, but you should have
the latest version if possible. At the time of this writing, the latest Xcode
version is 13.4.1. The exercises and sample code were tested on this Xcode
version. This implies your Mac will need to run macOS Monterey as Xcode
13 cannot run on anything lower than Monterey.

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.

Listing 1-1. A simple program that runs from top to bottom

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.

This is called procedural programming, because you write simple


statements, and they are executed from top to bottom. Even if you add
statements that can alternate the execution flow, it’s still easy to follow.
If you a call function when working with procedural programming, your
program will “jump” to a different place in memory and execute its

© Andrés Ibañez Kautsch 2023 1


A. I. Kautsch, Modern Concurrency on Apple Platforms,
https://doi.org/10.1007/978-1-4842-8695-1_1
Chapter 1 Introduction

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.

Important Concepts to Know


Concurrency and asynchronous programming are very wide topics. While
I’d love to cover everything, it would go out of the scope of this book.
Instead, we will define four important concepts that will be relevant while
we explore Apple’s async/await system, introduced in 2021. We will
define them with as few words as possible, because it’s important that you
keep them in mind while you work through the chapters of this book. The
concepts of the new system itself will be covered in the upcoming chapters.

2
Chapter 1 Introduction

Note Apple is not the original creator of the async/await


system. The technology has been used in other platforms in the
past. Microsoft announced C# would get async/await support in
2011, and C# with these features was officially released to the public
in 2012.

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.

Concurrency and Asynchronous Programming


Concurrency is the ability of a thread (or your program) to deal with
multiple things at once. It may be responding to different events, such as
network handlers, UI event handlers, OS interruptions, and more. There
may be multiple threads and all of them can be concurrent.

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.

Listing 1-2. Biometric unlock is an asynchronous task

func requestBiometricUnlock() {
    let context = LAContext()

    var error: NSError? = nil

    let canEvaluate = context.canEvaluatePolicy(


.deviceOwnerAuthenticationWithBiometrics, error: &error)

    if canEvaluate {
        if context.biometryType != .none {
            // (1)
            context.evaluatePolicy(
                .deviceOwnerAuthenticationWithBiometrics,
                localizedReason: "To access your data") {
(success, error) in
                // (2)
                if success {
                    // ...
                }
            }
        }
    }
}

(1) calls context.evaluatePolicy, which is a concurrent call. This


will ask the system to suspend your app so it can take over. The system will
request permission to use biometrics while your app is suspended. The

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.

Note If you are thinking on implementing biometric unlock to


resources within your app, please don’t use the code above. It has
been simplified to explain how concurrency works, and it doesn't
have the right safety measures to protect your user's data.

Multithreading is the act of running multiple tasks at once. Multiple


threads (hence its name – multithreading) are usually involved. Many tasks
can be running at the same time in the context of your app. Downloading
multiple images from the internet at the same time or downloading a file
from your web browser while you open some tabs are some examples of
multithreading. This allow us to run tasks in parallel and is sometimes
called parallelism.

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. Deadlocking in action

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?

Solving the Deadlocking Problem


The deadlock problem has many established solutions. Mutex and
Semaphores being the most used ones. There is also Inter-process
communication through pipes, but we will not talk about it because it goes
beyond a single program.

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.

Figure 1-2. Using a mutex

Keep in mind that in this specific situation, it means that Thread A


and Thread B, while multithreaded, will not run strictly at the same time,
because Thread B needs the same resources as Thread A and it will wait for
them to be free. For this reason, it is important to identify tasks that can be
run in parallel before designing a multithreaded system.
8
Chapter 1 Introduction

Semaphores

A Semaphore is a type of lock, very similar to a mutex. With this solution, a


task will acquire a lock to a resource. Any other tasks that arrive and need
that resource will see that the resource is busy. When the original thread
frees the resources, it will signal interested parties that the resource is
free, and they will follow the same process of locking and signaling as they
interact with the resource.
Mutex and Semaphores sound similar, but the key difference lies in
what kind of resource they are locking, and if you ever find yourself in
a situation in which you need to decide what to protect (you won’t find
such a case when using async/await), it is important to think about what
makes sense in your use case. In general, a mutex can be used to protect
anything that doesn’t have any sort of execution on its own, such as
files, sockets, and other filesystem elements. Semaphores can be used to
protect execution in your program itself such us shared code execution
paths. These can be mutating functions or classes with shared state. There
may be cases in which executing a function by more than one thread will
have unintended consequences, and semaphores are a good tool for that.
For example, a function that writes a log to disk may be protected with a
semaphore because if multiple processes write to the log at the same time,
the log will become corrupted for the end user. Whoever has a semaphore
to the logging function needs to signal the other interested parties that it is
free so they can continue their execution.
A rule of thumb is to always add a timeout whenever a semaphore
is acquired and let processes timeout if they take too long. If this is
acceptable in the context of your program (no data corruption can
occur, or there can be no other unintentional consequences to canceling
tasks), consider adding a timeout so the semaphore is free again after a
certain period.

9
Chapter 1 Introduction

The use of semaphores will allow threads to synchronize, as they will


be able to coordinate the resources amongst each other so they both have
their fair usage of them.

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.

Figure 1-3. The dining philosophers problem

Figure 1-3 describes what is known as the dining philosophers problem,


and it is a classical representation of the starvation problem.
The problem illustrates five philosophers who want to eat at a shared
table. All of them have their own dishes, and there are five forks. However,
each philosopher needs two forks to eat. This means that only two

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

If a history log is being written to by any threads with no control, events


may be registered in a random order, and one thread may override the
line another thread just wrote. For this reason, the History Log should be
locked at the writing operation. A thread writing to it will get a mutex to the
log, and it will free it after it’s done using it. Anyone else needing to write to
the log will be waiting until the lock is freed.
Deadlocks and race conditions are very similar. The main difference,
in deadlocks, is we have multiple processes waiting for the other to finish,
whereas in race conditions we have both processes writing data and
corrupting it because no processes know that the resource in question is
being used by someone else. This means that the solutions for deadlocking
also apply to race conditions, so just block your resource with a mutex or
semaphore to guarantee exclusive access to the resource by one process
only. This is called an Atomic Operation.

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.

Existing Multithreading and Concurrency Tools


Apple offers some lower-level tools to write multithreaded and concurrent
code. We will show examples for some of them. They are listed from lower
level to higher level. The lower the level, the harder it is to use that system
correctly, and the less likely it is that you will encounter it in the real world
anyway. You should be aware of these tools so you can be prepared to work
with them if you ever need to.

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.

The Grand Central Dispatch (GCD)


We will now talk about the first high-level tool for high-level concurrency
and multithreading on Apple platforms. The Grand Central Dispatch
(simply called GCD from here on) is so ubiquitous throughout Apple’s
SDKs that you have likely used it even without knowing. I’d be surprised if
I met a developer who has never written a line like Listing 1-3.

15
Chapter 1 Introduction

Listing 1-3. Calling code on the main thread

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.

Listing 1-4. The pyramid of doom

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.

The NSOperation APIs


Sitting at a higher level than the GCD, the NSOperation APIs are a high-­
level tool to do multithreading. Despite the fact they are not as widely
known as the GCD, they found their place in some parts of the SDK. For
example, the CloudKit APIs make use of this technology.
This one truly abstracts a lot of the pains of multithreading for you,
but it loses a lot of flexibility. You will never have to manage locks or
semaphores with it, and in turn the API is simple. This tool abstracts so
many details to the point that you don’t even know if tasks you run with it
are running on different threads. If the system thinks it makes sense to run
your tasks in the same thread, it will do that. Otherwise, it may honor your
intention of running in multiple threads. It may even choose to run your

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.

Listing 1-5. Multithreaded usage of the NSOperation APIs


func startCounting() {
    /// We will need a queue for this.
    let operationQueue = OperationQueue()

    /// You can give your queue an optional name, if you need
to identify it later.
    operationQueue.name = "Counting queue"

    /// This will just count from 1 to 10...


    let from1To10 = BlockOperation {
        for i in (1 ... 10) {
            print(i)
        }
    }

    /// ... and this from 11 to 20


    let from11To20 = BlockOperation {
        for i in (11 ... 20) {
            print(i)
        }
    }

    /// Add the operations to the queue


    operationQueue.addOperation(from1To10)
    operationQueue.addOperation(from11To20)

    /// To ensure the program doesn't exit early while the
operations are running.
    operationQueue.waitUntilAllOperationsAreFinished()
}

18
Chapter 1 Introduction

Using these APIs is very simple. You begin by creating an


OperationQueue. This queue’s responsibility is to execute any task you add to
it. You can also give it a name to refer to it later, or if you need to search for it.
In Listing 1-5, we create two tasks (in this case, instances of
BlockOperation – the original API is in Objective-C, so in Swift, it’s a
closure), from1To10 and from11To20. They begin executing as soon as they
are submitted to the queue via the OperationQueue.addOperation call.
In this example, you will see that the numbers get printed almost in an
interleaved manner. The results you get are going to be different each time
you run the program.
It’s very easy to run multiple tasks at once, but what if you want to
run one task after the other because one of them depends on the data of
another, or simply because it makes sense to do so?
In that case, BlockOperation has a method that allows you to define
an operation as a dependency of another. If you make from1to10 a
dependency of from11to20, then the number sequence will be printed in
the order you expect. Listing 1-6 modifies a portion of Listing 1-5 to print
the numbers in order by creating a dependency.

Listing 1-6. Adding operations as dependencies of other operations


// Ensure the numbers print in order. We do this before adding
the operations to the queue.
from11To20.addDependency(from1To10)

/// Add the operations to the queue


operationQueue.addOperation(from1To10)
operationQueue.addOperation(from11To20)

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

Using the NSOperation APIs is simple, as you have evidenced. It can


still be a great tool when you have simple multithreaded needs.

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.

Listing 1-7. async/await in action

@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

• The Grand Central Dispatch (GCD)

• NSOperation and related APIs

• async/await

Most developers will never need to go down to the pthread or


NSThread level. The GCD, and NSOperation APIs provide higher-level
abstractions, whereas “async/await” not only will provide abstractions
for the multithreading primitives, but it will also provide a whole system
that most developers can use without even knowing what the components
underneath are. This new system allows us to write shorter code that is
easier to read and easier to write. It’s worth studying this new system in
detail, and that’s what this book is about.

22
Chapter 1 Introduction

Exercises
ANSWER THE QUESTIONS

1. When using traditional concurrency tools, what are some pitfalls


you can fall into if you don’t use them correctly?

2. Prior to async/await, what were some concurrency tools


available on Apple’s platforms?

3. What advantages does async/await have over other,


lower-­level concurrency tools?

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).

 losures and Their Place in Concurrency


C
and Multithreading
To work through this chapter, I recommend you get the sample project
“Chapter 2 - Social Media App”. First, we will talk about the existing code
in the app that makes use of closure-based concurrency, and later we will
modify it to use async/await instead.
© Andrés Ibañez Kautsch 2023 25
A. I. Kautsch, Modern Concurrency on Apple Platforms,
https://doi.org/10.1007/978-1-4842-8695-1_2
Chapter 2 Introducing async/await

The sample app consists of two different views.


The first view shows a simple button you can tap to authenticate with
biometrics, as shown in Figure 2-1.

Figure 2-1. Entry point of the Social Media App

And the second screen, Figure 2-2, shows a profile view once
successfully authenticated:

26
Chapter 2 Introducing async/await

Figure 2-2. Main profile view of the Social Media App

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.

Closure-based concurrency is, at the time of this writing, present in


most apps that use concurrency. Open the file UserAPI.swift and you will
see the code in Listing 2-1, which looks all too familiar.

27
Chapter 2 Introducing async/await

Listing 2-1. Downloading some data from a server and parsing it to


show it to the user

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

This code may look intimidating to newcomers, but seasoned


developers can tell what’s going on. The code above will create a
URLSessionDataTask, which is the Foundation object we have for
downloading data from the network via HTTP. We need to call resume()
on the newly defined dataTask in order to actually perform a download.
Once the task is finished, we check if we have an error. If we do, we call the
function’s completionHandler by not giving it any userInfo and giving
it an Error instead. If we do have some data, we will try to parse it into a
UserInfo object. If there is an error in the parsing process, we will once
again call the handler with an error. If it succeeds, we got what we wanted,
and we call the handler with the newly downloaded object.
You may have seen variations of this function. For example, some
developers may choose to define the function as taking two closures
where one only takes the userInfo object and the other one only takes an
error, so the closures called are different for each case. This will allow you
to get rid of the optionals, but if there is common code that needs to run
regardless of the case, you will have longer code. Listing 2-2 shows how
this would be done.

Listing 2-2. An alternative way to deal with completion handlers


func fetchUserInfo(
    success: @escaping (_ userInfo: UserInfo) -> Void,
    failure: @escaping (_ error: Error) -> Void
) {
    //...
}

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.

Listing 2-3. Calling the fetchUserInfo method

private func fetchUserInfo() {


    let userApi = UserAPI()
    print("Beginning data download")
    print("Downloading User info")
    self.loadingStatus = .loading
    userApi.fetchUserInfo { userInfo, error in
        DispatchQueue.main.async {
            print("User info downloaded")
            if let error = error {
                self.loadingStatus = .error(error)
            } else if let userInfo = userInfo {
                self.loadingStatus = .idle
                self.userInfo = userInfo
            }
        }
    }
    print("Ending function")
}

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:

Beginning data download


Downloading User info

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.

Fig. 6.—Common Sturgeon, Acipenser sturio Mitchill. Potomac River.

The sturgeons are sluggish, clumsy, bottom-feeding fish. The mouth,


underneath the long snout, is very protractile, sucker-like, and
without teeth. Before it on the under side of the snout are four long
feelers. Ordinarily the sturgeon feeds on mud and snails with other
small creatures, but I have seen large numbers of Eulachon
(Thaleichthys) in the stomach of the Columbia River sturgeon
(Acipenser transmontanus). This fish and the Eulachon run in the
Columbia at the same time, and the sucker-mouth of a large
sturgeon will draw into it numbers of small fishes who may be
unsuspiciously engaged in depositing their spawn. In the spawning
season in June these clumsy fishes will often leap wholly out of the
water in their play. The sturgeons have a rough skin besides five
series of bony plates which change much with age and which in very
old examples are sometimes lost or absorbed in the skin. The
common sturgeon of the Atlantic on both shores is Acipenser sturio.
Acipenser huso and numerous other species are found in Russia and
Siberia. The great sturgeon of the Columbia is Acipenser
transmontanus, and the great sturgeon of Japan Acipenser kikuchii.
Smaller species are found farther south, as in the Mediterranean and
along the Carolina coast. Other small species abound in rivers and
lakes. Acipenser rubicundus is found throughout the Great Lake
region and the Mississippi Valley, never entering the sea. It is four to
six feet long, and at Sandusky, Ohio, in one season 14,000 sturgeons
were taken in the pound nets. A similar species, Acipenser mikadoi,
is abundant and valuable in the streams of northern Japan.

Fig. 7.—Lake Sturgeon, Acipenser rubicundus Le Sueur. Ecorse,


Mich.

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 shovel-nosed sturgeon (Scaphirhynchus platyrhynchus) has lost


the spiracles, the tail is more slender, its surface wholly bony, and
the snout is broad and shaped like a shovel. The single species of
Scaphirhynchus abounds in the Mississippi Valley, a fish more
interesting to the naturalist than to the fisherman. It is the smallest
of our sturgeons, often taken in the nets in large numbers.
In Scaphirhynchus the tail is covered by a continuous coat of mail.
In Kessleria[6] fedtschenkoi, rossikowi, and other Asiatic species the
tail is not mailed.
6. These species have also been named Pseudoscaphirhynchus. Kessleria is the
earlier name, left undefined by its describer, although the type was indicated.
Order Selachostomi: the Paddle-fishes.—Another type of
Ganoids, allied to the sturgeons, perhaps still further degenerate, is
that of the paddle-fishes, called by Cope Selachostomi (σέλαχος,
shark; στόμα, mouth). This group consists of a single family,
Polyodontidæ, having apparently little in common with the other
Ganoids, and in appearance still more suggestive of the sharks. The
common name of paddle-fishes is derived from the long flat blade in
which the snout terminates. This extends far beyond the mouth, is
more or less sensitive, and is used to stir up the mud in which are
found the minute organisms on which the fish feeds. Under the
paddle are four very minute barbels corresponding to those of the
sturgeons. The vernacular names of spoonbill, duckbill cat, and
shovel-fish are also derived from the form of the snout. The skin is
nearly smooth, the tail is heterocercal, the teeth are very small, and
a long fleshy flap covers the gill-opening. The very long and slender
gill-rakers serve to strain the food (worms, leeches, water-beetles,
crustaceans, and algæ) from the muddy waters from which they are
taken. The most important part of this diet consists of
Entomostracans. The single American species, Polyodon spathula,
abounds through the Mississippi Valley in all the larger streams. It
reaches a length of three or four feet. It is often taken in the nets,
but the coarse tough flesh, like that of our inferior catfish, is not
much esteemed. In the great rivers of China, the Yangtse and the
Hoang Ho, is a second species, Psephurus gladius, with narrower
snout, fewer gill-rakers, and much coarser fulcra on the tail. The
habits, so far as known, are much the same.

Fig. 9.—Paddle-fish, Polyodon spathula (Walbaum). Ohio River.

Fig. 10.—Paddle-fish. Polyodon Spathula (Walbaum). Ohio River.

Fig. 11.—Psephurus gladius Günther. Yangtse River. (After Günther.)


Crossopholis magnicaudatus of the Green River Eocene shales is a
primitive member of the Polyodontidæ. Its rostral blade is shorter
than that of Polyodon, and the body is covered with small thin
scales, each in the form of a small grooved disk with several
posterior denticulations, arranged in oblique series but not in
contact. The scales are quadrate in form, and more widely separated
anteriorly than posteriorly. As in Polyodon, the teeth are minute and
there are no branchiostegals. The squamation of this fish shows that
Polyodon as well as Acipenser may have sprung from a type having
rhombic scales. The tail of a Cretaceous fish, Pholidurus disjectus
from the Cretaceous of Europe, has been referred with doubt to this
family of Polyodontidæ.
Order Pycnodonti.—In the extinct order Pycnodonti, as recognized
by Dr. O. P. Hay, the notochord is persistent and without ossification,
the body is very deep, the teeth are always blunt, the opercular
apparatus is reduced, the dorsal fin many-rayed, and the fins
without fulcra. The scales are rhombic, but are sometimes wanting,
at least on the tail. Many genera and species of Pycnodontidæ are
described, mostly from Triassic and Jurassic rocks of Europe. Leading
European genera are Pycnodus, Typodus (Mesodon), Gyrodus, and
Palæobalistum. The numerous American species belong to Typodus,
Cœlodus, Pycnodus, Hadrodus, and Uranoplosus. These forms have
no affinity with Balistes, although there is some resemblance in
appearance, which has suggested the name of Palæobalistum.
Fig. 12.—Gyrodus hexagonus Agassiz. Family Pycnodontidæ.
Lithographic Shales.

Woodward places these fishes with the Semionotidæ and


Halecomorphi in his suborder of Protospondyli. It seems preferable,
however, to consider them as forming a distinct order.
Fig. 13.—Mesturus verrucosus Wagner. Family Pycnodontidæ. (After
Woodward.)

Order Lepidostei.—We may place, following Eastman's edition of


Zittel, the allies and predecessors of the garpike in a single order, for
which Huxley's name Lepidostei may well be used. In this group the
notochord is persistent, and the vertebræ are in various degrees of
ossification and of different forms. The opercles are usually
complete, the branchiostegals present, and there is often a gular
plate. There is no infraclavicle and the jaws have sharp teeth. The
fins have fulcra, and the supports of the fins agree in number with
the rays. The tail is more or less heterocercal. The scales are
rhombic, arranged in oblique series, which are often united above
and below with peg-and-socket articulations. This group contains
among recent fishes only the garpikes (Lepisosteus). They are
closely allied to the Palæoniscidæ, but the skeleton is more highly
ossified. On the other hand they approach very closely to the
ancestors of the bowfin, Amia. One genus, Acentrophorus, appears
in the Permian; the others are scattered through Mesozoic and
Tertiary rocks, the isolated group of gars still persisting. In the gars
the vertebræ are concavo-convex, with ball-and-socket joints. In the
others the vertebræ are incomplete or else double-concave, as in
fishes generally.
For the group here called Lepidostei numerous other names have
been used corresponding wholly or in part. Rhomboganoidea of Gill
covers nearly the same groups; Holostei of Müller and Hyoganoidea
of Gill include the Halecomorphi also; Ginglymodi of Cope includes
the garpikes only, while Ætheospondyli of Woodward includes the
Aspidorhynchidæ and the garpikes.

Fig. 14.—Semionotus kapffi Fraas, restored. Family Semionotidæ.


(After Fraas, per Nicholson.)

The Semionotidæ (Stylodontidæ) are robust-bodied Ganoids, having


the vertebræ developed as rings, the jaws with several rows of
teeth, those of the outer row styliform.
Semionotus bergeri is a well-known species, with the body
moderately elongate. Semionotus agassizi and many other species
occur in the Triassic of the Connecticut valley and in New Jersey. The
body is very deep in the related genus Dapedium, and the head is
covered with strong bony plates. Dapedium politum is a well-known
species of the English Triassic. Tetragonolepis (Pleurolepis) is a
similar form, very deep and compressed, with strong, firm scales.
In the extinct family of Lepidotidæ the teeth are conical or chisel-
shaped, while blunt or molar teeth are on the inside of the mouth,
which is small, and the suspensorium of the mandible is vertical or
inclined forward. The body is robust-fusiform, covered with
rhomboid scales; the vertebræ form rings about the notochord; the
teeth are either sharp or blunt. The dorsal fin is short, with large
fulcra.
The best known of the numerous genera are Lepidotes, rather
elongate in body, with large, blunt teeth. Of the many species of
Lepidotes, Lepidotes elvensis abounds in the English and German
Triassic, and Lepidotes minor in the English Triassic. Another well-
known European species is Lepidotes mantelli.

Fig. 15.—Dapedium politum Leach, restored. Family Semionotidæ.


(After Woodward.)
The Isopholidæ (Eugnathidæ) differ from the families last named in
the large pike-like mouth with strong teeth. The mandibular
suspensorium is inclined backwards. The body is elongate, the
vertebræ forming incomplete rings; the dorsal fin is short with large
fulcra.
Isopholis dentosus is found with numerous other species in the
British Triassic. Caturus furcatus is especially characteristic of Triassic
rocks in Germany. Ptycholepis marshi occurs in the Connecticut
valley.

Fig. 16.—Tetragonolepis semicinctus Brown. Lias. Family


Semionotidæ. (After Woodward.)

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.

Fig. 17.—Isopholis orthostomus (Agassiz). Lias. (After Woodward.)

Fig. 18.—The Long-nosed Garpike, Lepisosteus osseus (Linnæus).


Fox River, Wisconsin. (From nature; D. S. Jordan and M. L.
McDonald, 1874.)

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.)

Fig. 20.—Notagogus pentlandi Agassiz. Jurassic. Family


Macrosemiidæ. (After Woodward.)
Fig. 21.—Ptycholepis curtus Egerton. Lias. Family Isopholidæ. (After
Woodward.)

Of Pholidophorus, with scales joined by peg-and-socket joints and


uniform in size, there are many species. Pholidophorus latiusculus
and many others are found in the Triassic of England and the
Continent. Pholidophorus americanus occurs in the Jurassic of South
Dakota. Pleuropholis, with the scales on the lateral line, which runs
very low, excessively deepened, is also widely distributed. I have
before me a new species from the Cretaceous rocks near Los
Angeles. The Archæomænidæ differ from Pholidophoridæ in having
cycloid scales. In both families the vertebræ are reduced to rings
about the notochord. From fishes allied to the Pholidophoridæ the
earliest Isospondyli are probably descended.
Fig. 22.—Pholidophorus crenulatus Egerton. Lias. (After
Woodward.)

In the Aspidorhynchidæ the snout is more or less produced, the


mandible has a distinct presymphysial bone, the vertebræ are
double-concave or ring-like, and the fins are without fulcra. This
family constitutes the suborder Ætheospondyli. In form these fishes
resemble Albula and other modern types, but have mailed heads and
an ancient type of scales. Two genera are well known,
Aspidorhynchus and Belonostomus. Aspidorhynchus acutirostris
reaches a length of three feet, and is found in the Triassic
lithographic stone of Bavaria. Other species occur in rocks of
Germany and England.
Belonostomus has the snout scarcely produced. Belonostomus
sphyrænoides is the best known of the numerous species, all of the
Triassic, Jurassic, and Cretaceous.
Family Lepisosteidæ.—The family of Lepisosteidæ, constituting
the suborder Ginglymodi (γιγγλυμός, hinge), is characterized
especially by the form of the vertebræ.
These are opisthocœlian, convex in front and concave behind, as in
reptiles, being connected by ball-and-socket joints. The tail is
moderately heterocercal, less so than in the Halecomorphi, and the
body is covered with very hard, diamond-shaped, enameled scales in
structure similar to that of the teeth. A number of peculiar
characters are shown by these fishes, some of them having often
been regarded as reptilian traits. Notable features are the elongate,
crocodile-like jaws, the upper the longer, and both armed with
strong teeth. The mandible is without presymphysial bone. The fins
are small with large fulcra, and the scales are nearly uniform in size.
All the species belong to a single family, Lepisosteidæ, which
includes the modern garpikes and their immediate relatives, some of
which occur in the early Tertiary. These voracious fishes are
characterized by long and slender cylindrical bodies, with enameled
scales and mailed heads and heterocercal tail. The teeth are sharp
and unequal. The skeleton is well ossified, and the animal itself is
extremely voracious. The vertebræ, reptile-like, are opisthocœlian,
that is, convex in front, concave behind, forming ball-and-socket
joints. In almost all other fishes they are amphicœlian or double-
concave, the interspace filled with gelatinous substance. The recent
species, and perhaps all the extinct species also, belong to the single
genus Lepisosteus (more correctly, but also more recently, spelled
Lepidosteus). Of existing forms there are not many species, three to
five at the most, and they swarm in the lakes, bayous, and sluggish
streams from Lake Champlain to Cuba and along the coast to Central
America. The best known of the species is the long-nosed garpike,
Lepisosteus osseus, which is found throughout most of the Great
Lake region and the Mississippi Valley, and in which the long and
slender jaws are much longer than the rest of the head. The garpike
frequents quiet waters and is apparently of sleepy habit. It often lies
quiet for a long time, carried around and around by the eddies. It
does not readily take the hook and seldom feeds in the aquarium. It
feeds on crayfishes and small fishes, to which it is exceedingly
destructive, as its bad reputation indicates. Fishermen everywhere
destroy it without mercy. Its flesh is rank and tough and unfit even
for dogs.
In the young garpike the caudal fin appears as a second dorsal and
anal, the filamentous tip of the tail passing through and beyond it.
The short-nosed garpike, Lepisosteus platystomus, is generally
common throughout the Mississippi Valley. It has a short broad
snout like the alligator-gar, but seldom exceeds three feet in length.
In size, color, and habits it agrees closely with the common gar,
differing only in the form of the snout. The form is subject to much
variation, and it is possible that two or more species have been
confounded.

Fig. 23.—Alligator-gar, Lepisosteus tristœchus (Bloch). Cuba.

The great alligator-gar, Lepisosteus tristœchus, reaches a length of


twenty feet or more, and is a notable inhabitant of the streams
about the Gulf of Mexico. Its snout is broad and relatively wide, and
its teeth are very strong. It is very destructive to all sorts of food-
fishes. Its flesh is worthless, and its enameled scales resist a spear
or sometimes even shot. It breathes air to a certain extent by its
lungs, but soon dies in foul water, not having the tenacity of life seen
in Amia.
Embryology of the Garpike.—Mr. Alexander Agassiz has given an
account of the embryology of the garpike, of which the following is
an abstract:
"The garpike comes up the St. Lawrence in May, lays its eggs about
the 20th, and then disappears. The eggs are large, viscous, stick fast
in an isolated way to whatever they fall upon, and look much like
those of toads, having a large outer membrane and a small yolk.
Artificial fecundation failed, but about 500 naturally-laid eggs were
secured, of which all but 30 perished through mold. The young
began to hatch in six days. Out of 30 young hatched, 27 lived until
the 15th of July. Connection with the sharks appears in the similarity
of the branchial arches and by the presence of the lateral fold in
which the pectoral fins are formed; the way the tail is developed is
very like that of the bony fishes. Among the Ganoids it appears, as
well as in ordinary fishes, the dorsal cord is straight at first, then
assumes a slightly upward curve at the extremity, when finally there
appears the beginning of a lobe underneath, pointing to a complete
heterocercal tail. All this is as in the bony fishes, but this is the
permanent condition of the garpike, while in the bony fishes the
extremity of the dorsal cord becomes extinct. The mode of
development of the pectoral lobe (very large in this species)
furnishes another resemblance. In the brain, and in the mode of
formation of the gills, a likeness to the sharks is noticeable. The
young garpikes move very slowly, and seem to float quietly, save an
exceedingly rapid vibration of the pectorals and the tip of the tail.
They do not swim about much, but attach themselves to fixed
objects by an extraordinary horseshoe-shaped ring of sucker-
appendages about the mouth. These appendages remain even after
the snout has become so extended that the ultimate shape is hinted
at; and furthermore, it is a remnant of this feature that forms the
fleshy bulb at the end of the snout in the adult. The investigations
thus far show that the young garpike has many characteristics in
common with the sharks and skates, but it is not so different from
the bony fishes as has been supposed."
Fossil Garpikes.—A number of fossil garpikes, referred by Cope to
the genus Clastes and by Eastman and Woodward to Lepidosteus,
are found in the Eocene of Europe and America. The most perfect of
these remains is called Lepisosteus atrox, upward of four feet long,
as large as an alligator-gar, which the species much resembles.
Although found in the Eocene, Dr. C. R. Eastman declares that "it
has no positively archaic features. If we inquire into the more
remote or pre-Eocene history of Lepidosteids, palæontology gives no
answer. They blossom forth suddenly and fully differentiated at the
dawn of the Tertiary, without the least clue to their ancestry,
unheralded and unaccompanied by any intermediate forms, and they
have remained essentially unchanged ever since."
Another fossil species is Lepisosteus fimbriatus, from the Upper
Eocene of England. Scales and other fragments of garpikes are
found in Germany, Belgium, and France, in Eocene and Miocene
rocks. On some of these the nominal genera Naisia, Trichiurides, and
Pneumatosteus are founded. Clastes, regarded by Eastman as fully
identical with Lepisosteus, is said to have the "mandibular ramus
without or with a reduced fissure of the dental foramen, and without
the groove continuous with it in Lepisosteus. One series of large
teeth, with small ones external to them on the dentary bone." Most
of the fossil forms belong to Clastes, but the genus shows no
difference of importance which will distinguish it from the ordinary
garpike.
Fig. 24.—Lower jaw of Amia calva Linnæus, showing the gular
plate.

Order Halecomorphi.—To this order belong the allies, living or


extinct, of the bowfin (Amia), having for the most part cycloid scales
and vertebræ approaching those of ordinary fishes. The resemblance
to the Isospondyli, or herring group, is indicated in the name (Halec,
a herring; μορφή, form). The notochord is persistent, the vertebræ
variously ossified. The opercles are always complete. The
branchiostegals are broad and there is always a gular plate. The
teeth are pointed, usually strong. There is no infraclavicle. Fulcra are
present or absent. The supports of the dorsal and anal are equal in
number to the rays. Tail heterocercal. Scales thin, mostly cycloid, but
bony at base, not jointed with each other. Mandible complex, with
well-developed splenial rising into a coronoid process, which is
completed by a distinct coronoid bone. Pectoral fin with more than
five actinosts; scales ganoid or cycloid. In the living forms the air-
bladder is connected with the œsophagus through life; optic chiasma
present; intestine with a spiral valve. This group corresponds to the
Amioidei of Lütken and essentially to the Cycloganoidei of Gill. The
Protospondyli (προτός, before; σπόνδυλος, vertebra) of Woodward
contains essentially the same elements.
Pachycormidæ.—In the family of Pachycormidæ the notochord is
persistent, the ethmoids and vomer fused and projecting between
the maxillaries to form the prominent snout, the teeth large, the
body fusiform, the dorsal short, with slender rays and few fulcra or
none, and the scales are thin and rhombic. The numerous species
are characteristic of the Triassic, Jurassic, and Cretaceous. In
Sauropsis (longimana) the body is elongate, and the pectoral fins are
large and sickle-shaped. Euthynotus has small fulcra. In
Pachycormus (macropterus, esocinus, etc.) the form is robust and
the ventral fins are wanting. In Hypsycormus ventrals are present,
and the caudal deeply forked.
In the American family of Protosphyrænidæ the jaws are armed with
very strong teeth, as in the Barracuda, which, however, the species
do not resemble in other respects. Protosphyræna nitida, perniciosa,
and numerous other extinct forms, some of them of large size, were
voracious inhabitants of the Cretaceous seas, and are found fossil,
especially in North Carolina and Kansas. Numerous species called
Erisichthe and Pelecopterus are all referred by Hay to
Protosphyræna. In this family the scapula and coracoids are ossified,
and perhaps the vertebræ also, and, as Dr. Hay has recently
suggested, the Protosphyrænidæ may really belong to the
Isospondyli. In any event, they stand on the border-line between the
most fish-like of the Ganoids and the most archaic of the bony
fishes.
The Liodesmidæ (genus Liodesmus) are much like Amia, but the
notochord is persistent, its sheath without ossification. Liodesmus
gracilis and L. sprattiformis occur in the lithographic stones of
Bavaria. Woodward places Liodesmus with Megalurus among the
Amiidæ.
The Bowfins: Amiidæ.—The Amiidæ have the vertebræ more
complete. The dorsal fin is many-rayed and is without distinct fulcra.
The diamond-shaped enameled scales disappear, giving place to
cycloid scales, which gradually become thin and membranous in
structure. A median gular plate is developed between the
branchiostegals. The tail is moderately heterocercal, and the head
covered with a bony coat of mail.
The family of Amiidæ contains a single recent species, Amia calva,
the only living member of the order Halecomorphi. The bowfin, or
grindle, is a remarkable fish abounding in the lakes and swamps of
the Mississippi Valley, the Great Lake region, and southward to
Virginia, where it is known by the imposing but unexplained title of
John A. Grindle. In the Great Lakes it is usually called "dogfish,"
because even the dogs will not eat it, and "lawyer," because,
according to Dr. Kirtland, "it will bite at anything and is good for
nothing when caught."
The bowfin reaches a length of two and one half feet, the male
being smaller than the female and marked by an ocellated black spot
on the tail. Both sexes are dark mottled green in color. The flesh of
the species is very watery, pasty, much of the substance evaporating
when exposed to the air. It is ill-flavored, and is not often used as
food. The species is very voracious and extremely tenacious of life.
Its well-developed lung enables it to breathe even when out of the
water, and it will live in the air longer than any other fish of
American waters, longer even than the horned pout (Ameiurus) or
the mud-minnow (Umbra). As a game fish the grindle is one of the
very best, if the angler does not care for the flesh of what he
catches, it being one of the hardest fighters that ever took the hook.

Fig. 25.—Bowfin (female), Amia calva Linnæus. Lake Michigan.

The Amiidæ retain many of the Ganoid characters, though


approaching more nearly than any other of the Ganoids to the
modern herring tribe. For this reason the name Halecomorphi (shad-
formed) was given to this order by Professor Cope. The gular plate
found in Amia and other Ganoids reappears in the herring-like family
of Elopidæ, which includes the tarpon and the ten-pounder.
Woodward unites the extinct genera called Cyclurus, Notæus,
Amiopsis, Protamia, Hypamia, and Pappichthys with Amia.
Pappichthys (corsoni, etc.), from the Wyoming Eocene, is doubtless
a valid genus, having but one row of teeth in each jaw, and Amiopsis
is also recognized by Hay. Woodward refers to Amia the following
extinct species: Amia valenciennesi, from the Miocene of France;
Amia macrocephala, from the Miocene of Bohemia; and Amia ignota,
from the Eocene of Paris. Other species of Amia are known from
fragments. Several of these are from the Eocene of Wyoming and
Colorado. Some of them have a much shorter dorsal fin than that of
Amia calva and may be generically different.

Fig. 26.—Megalurus elegantissimus Wagner. Family Amiidæ. (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

he Subclass Teleostei, or Bony Fishes.—The fishes


which still remain for discussion constitute the great
subclass or series of Teleostei (τελεός, true; οστέον,
bone), or bony fishes. They lack wholly or partly the
Ganoid traits, or show them only in the embryo. The tail
is slightly, if at all, heterocercal; the actinosts of the pectoral fins are
few and large, rarely over five in number, except among the eels;
the fulcra disappear; the air-bladder is no longer cellular, except in
very rare cases, nor does it assist in respiration. The optic nerves are
separate, one running to each eye without crossing; the skeleton is
almost entirely bony, the notochord usually disappearing entirely
with age; the valves in the arterial bulb are reduced in number, and
the spiral valve of the intestines disappears. Traces of each of the
Ganoid traits may persist somewhere in some group, but as a whole
we see a distinct specialization and a distinct movement toward the
fish type, with the loss of characters distinctive of sharks, Dipnoans,
and Ganoids. In a general way the skeleton of all Teleosts
corresponds with that of the striped bass (see Figs. 22, 23, Vol. I),
and the visceral anatomy is in all cases sufficiently like that of the
sunfish (Fig. 16, Vol. I).
The mesocoracoid or precoracoid arch, found in all Ganoids, persists
in the less specialized types of bony fishes, although no trace of it is
found in the perch-like forms. With all this, there is developed
among the bony fishes an infinite variety in details of structure. For
this reason the Teleostei must be broken into many orders, and
these orders are very different in value and in degrees of
distinctness, the various groups being joined by numerous and
puzzling intergradations.
Order Isospondyli.—Of the various subordinate groups of bony
fishes, there can be no question as to which is most primitive in
structure, or as to which stands nearest the orders of Ganoids.
Earliest of the bony fishes in geological time is the order of
Isospondyli (ἴσος, equal; σπόνδυλος, vertebra), containing the allies,
recent or fossil, of the herring and the trout. This order contains
those soft-rayed fishes in which the ventral fins are abdominal, a
mesocoracoid or precoracoid arch is developed, and the anterior
vertebræ are unmodified and essentially similar to the others. The
orbitosphenoid is present in all typical forms. In certain forms of
doubtful affinity (Iniomi) the mesocoracoid is wanting or lost in
degeneration. Through the Isospondyli all the families of fishes yet
to be considered are apparently descended, their ancestors being
Ganoid fishes and, still farther back, the Crossopterygians.
Woodward gives this definition of the Isospondyli: "Notochord
varying in persistence, the vertebral centra usually complete, but
none coalesced; tail homocercal, but hæmal supports not much
expanded or fused. Symplectic bone present, mandible simple, each
dentary consisting only of two elements (dentary and articulo-
angular), with rare rudiments of a splenoid on the inner side.
Pectoral arch suspended from the cranium; precoracoid
(mesocoracoid) arch present; infraclavicular plates wanting. Pelvic
(ventral) fins abdominal. Scales ganoid only in the less specialized
families. In the living forms air-bladder connected with the
œsophagus in the adult; optic nerves decussating (without chiasma),
and intestine either wanting spiral valve or with an incomplete
representative of it."
The Classification of the Bony Fishes.—The classification of
fishes has been greatly complicated by the variety of names applied
to groups which are substantially but not quite identical one with
another. The difference in these schemes of classification lies in the
Welcome to our website – the perfect destination for book lovers and
knowledge seekers. We believe that every book holds a new world,
offering opportunities for learning, discovery, and personal growth.
That’s why we are dedicated to bringing you a diverse collection of
books, ranging from classic literature and specialized publications to
self-development guides and children's books.

More than just a book-buying platform, we strive to be a bridge


connecting you with timeless cultural and intellectual values. With an
elegant, user-friendly interface and a smart search system, you can
quickly find the books that best suit your interests. Additionally,
our special promotions and home delivery services help you save time
and fully enjoy the joy of reading.

Join us on a journey of knowledge exploration, passion nurturing, and


personal growth every day!

ebookmasss.com

You might also like