Управление потоком
Структурируйте код с помощью ветвлений, циклов и преждевременных выходов.
Swift предоставляет различные операторы управления потоком. К ним относятся while циклы для выполнения задачи многократно; if, guard и switch операторы для выполнения различных ветвей кода в зависимости от определенных условий; и операторы, такие как break и continue, для передачи управления потоком в другую точку вашего кода. Swift предоставляет for-in цикл, который упрощает итерацию по массивам, словарям, диапазонам, строкам и другим последовательностям. Swift также предоставляет defer операторы, которые оборачивают код, который необходимо выполнить при выходе из текущего области видимости.
Оператор switch Swift значительно мощнее, чем его аналог во многих языках семейства C. Включения могут соответствовать множеству различных шаблонов, включая соответствие интервалов, кортежей и преобразований к определенному типу. Сопоставленные значения в случае switch могут быть связаны с временными константами или переменными для использования в теле случая, а сложные условия соответствия могут быть выражены с помощью where предложения для каждого случая.
Циклы for-in
Вы используете цикл for-in для итерации по последовательности, такой как элементы массива, диапазоны чисел или символы в строке.
В этом примере используется цикл for-in для итерации по элементам массива:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack! Вы также можете перебирать словарь для доступа к его парам ключ-значение. Каждый элемент в словаре возвращается как кортеж (key, value) при итерации по словарю, и вы можете разложить члены кортежа (key, value), как явно заданные константы для использования в теле цикла for-in. В примере кода ниже ключи словаря раскладываются в константу, названную animalName, а значения словаря — в константу, названную legCount.
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// cats have 4 legs
// ants have 6 legs
// spiders have 8 legs Содержание Dictionary изначально неупорядочено, и итерация по нему не гарантирует порядок, в котором они будут извлечены. В частности, порядок, в котором вы добавляете элементы в Dictionary, не определяет порядок их итерации. Дополнительную информацию о массивах и словарях см. в разделе CollectionTypes.
Вы также можете использовать циклы for-in с числовыми диапазонами. В этом примере выводятся первые несколько элементов таблицы умножения на пять:
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25 Последовательность, по которой итерируется, представляет собой диапазон чисел от 1 до 5 включительно, как указано использованием оператора закрытого диапазона (...). Значение index устанавливается на первое число в диапазоне (1), и операторы внутри цикла выполняются. В этом случае цикл содержит только один оператор, который выводит элемент таблицы умножения на пять для текущего значения index. После выполнения оператора значение index обновляется, содержа в себе второе значение в диапазоне (2), и функция print(_:separator:terminator:) вызывается снова. Этот процесс продолжается до достижения конца диапазона.
В приведенном выше примере index — это константа, значение которой автоматически устанавливается в начале каждой итерации цикла. Таким образом, index не нужно объявлять перед использованием. Она неявно объявляется просто благодаря её включению в объявление цикла, без необходимости ключевого слова объявления let.
Если вам не нужно каждое значение из последовательности, вы можете пропустить значения, используя символ подчеркивания вместо имени переменной.
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// Prints "3 to the power of 10 is 59049" В приведенном выше примере вычисляется значение одного числа в степени другого (в данном случае, 3 в степени 10). Оно умножает начальное значение 1 (то есть 3 в степени 0) на 3 десять раз, используя закрытый диапазон, который начинается с 1 и заканчивается 10. Для этого расчёта индивидуальные значения счётчика при каждом прохождении цикла не нужны — код просто выполняет цикл нужное количество раз. Использование символа подчеркивания (_) вместо переменной цикла приводит к игнорированию отдельных значений и не предоставляет доступа к текущему значению во время каждой итерации цикла.
В некоторых ситуациях вам может не понадобиться использовать закрытые диапазоны, которые включают обе точки. Представьте себе маркировку минут на циферблате часов. Вам нужно разместить 60 делений, начиная с 0 минуты. Используйте оператор полуоткрытого диапазона (..<), чтобы включить нижнюю границу, но не верхнюю. Дополнительную информацию о диапазонах см. в <doc:BasicOperators#Range-Operators>.
let minutes = 60
for tickMark in 0..<minutes {
// render the tick mark each minute (60 times)
} Некоторые пользователи могут захотеть меньше делений на своем интерфейсе. Они могут предпочесть одно деление каждые 5 минут. Используйте функцию stride(from:to:by:) для пропуска нежелательных делений.
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// render the tick mark every 5 minutes (0, 5, 10, 15 ... 45, 50, 55)
} Закрытые диапазоны также доступны, используя stride(from:through:by:) вместо этого:
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
// render the tick mark every 3 hours (3, 6, 9, 12)
} В приведенных выше примерах используется цикл for-in для итерации по диапазонам, массивам, словарям и строкам. Однако вы можете использовать этот синтаксис для итерации по любому набору данных, включая ваши собственные классы и типы наборов данных, при условии, что эти типы соответствуют протоколу Sequence.
Циклы while
Цикл while выполняет набор операторов до тех пор, пока условие не станет false. Такие циклы лучше использовать, когда количество итераций неизвестно до начала первой итерации. Swift предоставляет два вида циклов while:
-
whileвычисляет свое условие в начале каждого прохода по циклу. -
repeat-whileвычисляет свое условие в конце каждого прохода по циклу.
While
Цикл while начинается с вычисления одного условия. Если условие true, набор операторов повторяется до тех пор, пока условие не станет false.
Вот общий вид цикла while:
while <#condition#> {
<#statements#>
} В этом примере играется простая игра в Змейки и Лестницы (также известная как Ловцы и Лестницы):
Правила игры следующие:
- Доска имеет 25 клеток, и цель состоит в том, чтобы приземлиться на или за клеткой 25.
- Начальная клетка игрока — «клетка ноль», которая находится чуть ниже левого нижнего угла доски.
- На каждом ходу вы бросаете шестигранную кость и передвигаетесь на это число клеток, следуя горизонтальной траектории, обозначенной пунктирной стрелкой выше.
- Если ваш ход заканчивается внизу лестницы, вы поднимаетесь по этой лестнице.
- Если ваш ход заканчивается на голове змеи, вы спускаетесь по этой змее.
Доска игры представлена массивом Int значений. Ее размер основан на константе, названной finalSquare, которая используется для инициализации массива и также для проверки условия победы позже в примере. Поскольку игроки начинают вне доски, на «клетке ноль», доска инициализируется 26 нулевыми Int значениями, а не 25.
let finalSquare = 25 var board = [Int](repeating: 0, count: finalSquare + 1)
Затем некоторые клетки устанавливаются, чтобы иметь более специфические значения для змей и лестниц. Клетки с основанием лестницы имеют положительное число, чтобы поднять вас вверх по доске, а клетки с головой змеи — отрицательное число, чтобы спустить вас вниз по доске.
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
Клетка 3 содержит основание лестницы, которая поднимает вас на клетку 11. Для представления этого, board[03] равно +08, что эквивалентно целочисленному значению 8 (разница между 3 и 11). Для выравнивания значений и операторов явно используется унарный оператор плюса (+i) с унарным оператором минуса (-i) и числа меньше 10 дополняются нулями. (Ни один из стилистических приемов не является строго необходимым, но они приводят к более аккуратному коду.)
var square = 0
var diceRoll = 0
while square < finalSquare {
// roll the dice
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// move by the rolled amount
square += diceRoll
if square < board.count {
// if we're still on the board, move up or down for a snake or a ladder
square += board[square]
}
}
print("Game over!") В приведенном выше примере используется очень простой подход к броску кости. Вместо генерации случайного числа, он начинает с значения diceRoll, равного 0. При каждом прохождении цикла while diceRoll увеличивается на единицу и проверяется, не стало ли оно слишком большим. Всякий раз, когда это возвращаемое значение равно 7, бросок кости стал слишком большим и сбрасывается до значения 1. В результате получается последовательность diceRoll значений, которая всегда равна 1, 2, 3, 4, 5, 6, 1, 2 и так далее.
После броска кости игрок перемещается вперёд на diceRoll клеток. Возможно, что бросок кости мог переместить игрока за клетку 25, в этом случае игра заканчивается. Для обработки этой ситуации код проверяет, что square меньше свойства board массива count. Если square является допустимым, значение, хранящееся в board[square], добавляется к текущему значению square, чтобы переместить игрока вверх или вниз по любой лестнице или змее.
Примечание: Если эта проверка не выполняется,
board[square]может попытаться получить доступ к значению за пределами границ массиваboard, что приведет к ошибке во время выполнения.
Последующее выполнение текущего цикла while завершается, и проверяется условие цикла, чтобы определить, следует ли снова выполнять цикл. Если игрок переместился на или за клетку номер 25, условие цикла вычисляется как false, и игра заканчивается.
Цикл while подходит в этом случае, потому что длина игры не ясна в начале цикла while. Вместо этого цикл выполняется до тех пор, пока не будет выполнено определённое условие.
Repeat-While
Другая разновидность цикла while, известная как цикл repeat-while, выполняет один проход по блоку цикла сначала, перед рассмотрением условия цикла. Затем он продолжает повторять цикл, пока условие не станет false.
Примечание: Цикл
repeat-whileв Swift аналогичен циклуdo-whileв других языках.
Вот общий вид цикла repeat-while:
repeat {
<#statements#>
} while <#condition#> Вот пример игры «Змеи и лестницы», написанный как цикл repeat-while, а не цикл while. Значения finalSquare, board, square и diceRoll инициализируются точно так же, как и в цикле while.
let finalSquare = 25 var board = [Int](repeating: 0, count: finalSquare + 1) board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 var square = 0 var diceRoll = 0
В этой версии игры первым действием в цикле является проверка наличия лестницы или змеи. Если на доске нет лестницы, которая переводит игрока сразу на клетку 25, то выиграть, поднявшись по лестнице, невозможно. Поэтому безопасно проверять наличие змеи или лестницы в качестве первого действия в цикле.
В начале игры игрок находится на «клетки ноль». board[0] всегда равно 0 и не оказывает влияния.
repeat {
// move up or down for a snake or ladder
square += board[square]
// roll the dice
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// move by the rolled amount
square += diceRoll
} while square < finalSquare
print("Game over!") После проверки на змей и лестницы подбрасывается игральная кость, и игрок перемещается вперёд на diceRoll клеток. Затем выполнение текущей итерации цикла завершается.
Условие цикла (while square < finalSquare) такое же, как и раньше, но на этот раз оно не оценивается до конца первого прохода по циклу. Структура цикла repeat-while лучше подходит для этой игры, чем цикл while в предыдущем примере. В цикле repeat-while выше, square += board[square] всегда выполняется непосредственно после того, как условие цикла while подтверждает, что square всё ещё находится на доске. Это поведение исключает необходимость проверки границ массива, которая присутствовала в версии игры с циклом while.
Условные операторы
Часто полезно выполнять различные части кода в зависимости от определённых условий. Возможно, вы захотите запустить дополнительный фрагмент кода при возникновении ошибки или отобразить сообщение, когда значение становится слишком высоким или слишком низким. Для этого вы делаете части вашего кода условными.
Swift предоставляет два способа добавления условных ветвей в ваш код: оператор if и оператор switch. Обычно вы используете оператор if для оценки простых условий с несколькими возможными результатами. Оператор switch лучше подходит для более сложных условий с множеством возможных вариантов и полезен в ситуациях, когда шаблоны соответствия могут помочь выбрать подходящую ветвь кода для выполнения.
If
В простейшем виде оператор if имеет одно условие if. Он выполняет набор операторов только в том случае, если это условие true.
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
}
// Prints "It's very cold. Consider wearing a scarf." В примере проверяется, меньше ли температура 32 градусов по Фаренгейту (точка замерзания воды). Если да, то выводится сообщение. В противном случае сообщение не выводится, и выполнение кода продолжается после закрывающей фигурной скобки оператора if.
Оператор if может предоставить альтернативный набор операторов, известный как ветвь else, для ситуаций, когда условие if является false. Эти операторы обозначаются ключевым словом else.
temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
} else {
print("It's not that cold. Wear a T-shirt.")
}
// Prints "It's not that cold. Wear a T-shirt." Одна из этих двух ветвей всегда выполняется. Поскольку температура повысилась до 40 градусов по Фаренгейту, она уже не достаточно низкая, чтобы рекомендовать надевать шарф, и поэтому вместо этого срабатывает ветвь else.
Вы можете связать несколько операторов if вместе, чтобы рассмотреть дополнительные условия.
temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
print("It's really warm. Don't forget to wear sunscreen.")
} else {
print("It's not that cold. Wear a T-shirt.")
}
// Prints "It's really warm. Don't forget to wear sunscreen." Здесь был добавлен дополнительный оператор if для реакции на особенно тёплую температуру. Окончательная ветвь else остаётся и выводит ответ для любых температур, которые не слишком тёплые или слишком холодные.
Окончательная ветвь else является необязательной и может быть исключена, если набор условий не требует полноты.
temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
print("It's really warm. Don't forget to wear sunscreen.")
} Поскольку температура не достаточно низкая, чтобы вызвать условие if, или достаточно тёплая, чтобы вызвать условие else if, ни одно сообщение не выводится.
Swift предоставляет сокращённое написание оператора if, которое можно использовать при установке значений. Например, рассмотрим следующий код:
let temperatureInCelsius = 25
let weatherAdvice: String
if temperatureInCelsius <= 0 {
weatherAdvice = "It's very cold. Consider wearing a scarf."
} else if temperatureInCelsius >= 30 {
weatherAdvice = "It's really warm. Don't forget to wear sunscreen."
} else {
weatherAdvice = "It's not that cold. Wear a T-shirt."
}
print(weatherAdvice)
// Prints "It's not that cold. Wear a T-shirt." Здесь в каждой ветви устанавливается значение для константы weatherAdvice, которая выводится после оператора if.
Используя альтернативный синтаксис, известный как выражение if, вы можете записать этот код более лаконично:
let weatherAdvice = if temperatureInCelsius <= 0 {
"It's very cold. Consider wearing a scarf."
} else if temperatureInCelsius >= 30 {
"It's really warm. Don't forget to wear sunscreen."
} else {
"It's not that cold. Wear a T-shirt."
}
print(weatherAdvice)
// Prints "It's not that cold. Wear a T-shirt." В этой версии с выражением if каждая ветвь содержит единственное значение. Если условие ветви истинно, то значение этой ветви используется как значение всего выражения if при присваивании weatherAdvice. Каждая ветвь if имеет соответствующую ветвь else if или ветвь else, гарантируя, что одна из ветвей всегда соответствует и что выражение if всегда производит значение, независимо от того, какие условия истинны.
Поскольку синтаксис присваивания начинается вне выражения if, нет необходимости повторять weatherAdvice = внутри каждой ветви. Вместо этого каждая ветвь выражения if производит одно из трёх возможных значений для weatherAdvice, и присваивание использует это значение.
Все ветви выражения if должны содержать значения одного типа. Поскольку Swift проверяет тип каждой ветви отдельно, значения, такие как nil, которые могут использоваться с несколькими типами, препятствуют автоматическому определению типа выражения if. Вместо этого вам нужно указать тип явно — например:
let freezeWarning: String? = if temperatureInCelsius <= 0 {
"It's below freezing. Watch for ice!"
} else {
nil
} В коде выше одна ветвь выражения if имеет строковое значение, а другая — значение типа nil. Значение типа nil может быть использовано как значение для любого необязательного типа, поэтому вы должны явно указать, что freezeWarning является необязательной строкой, как описано в <doc:TheBasics#Type-Annotations>.
Альтернативный способ предоставления этой информации о типе — указать явный тип для nil вместо явного типа для freezeWarning:
let freezeWarning = if temperatureInCelsius <= 0 {
"It's below freezing. Watch for ice!"
} else {
nil as String?
} Выражение if может реагировать на непредвиденные ошибки, выбрасывая ошибку или вызывая функцию, такую как fatalError(_:file:line:), которая никогда не возвращает значение. Например:
let weatherAdvice = if temperatureInCelsius > 100 {
throw TemperatureError.boiling
} else {
"It's a reasonable temperature."
} В этом примере выражение if проверяет, выше ли прогнозируемая температура 100° C — точка кипения воды. Такая высокая температура заставляет выражение if выбросить ошибку .boiling вместо возвращения текстового резюме. Даже несмотря на то, что это выражение if может выбросить ошибку, вы не пишете try перед ним. Подробная информация о работе с ошибками приведена в ErrorHandling.
В дополнение к использованию выражений if в правой части присваивания, как показано в примерах выше, вы также можете использовать их в качестве значения, которое возвращает функция или замыкание.
Switch
Оператор switch рассматривает значение и сравнивает его со множеством возможных совпадающих шаблонов. Затем он выполняет соответствующий блок кода, основываясь на первом шаблоне, который успешно соответствует. Оператор switch предоставляет альтернативу оператору if для реакции на несколько потенциальных состояний.
В простейшем виде оператор switch сравнивает значение с одним или несколькими значениями того же типа.
switch <#some value to consider#> {
case <#value 1#>:
<#respond to value 1#>
case <#value 2#>,
<#value 3#>:
<#respond to value 2 or 3#>
default:
<#otherwise, do something else#>
} Каждый оператор switch состоит из нескольких возможных случаев, каждый из которых начинается с ключевого слова case. Помимо сравнения со специфическими значениями, Swift предоставляет несколько способов для каждого случая указать более сложные шаблоны сопоставления. Эти варианты описаны позже в этой главе.
Как и тело оператора if, каждый случай case — это отдельная ветвь выполнения кода. Оператор switch определяет, какая ветвь должна быть выбрана. Этот процесс известен как переключение на значение, которое рассматривается.
Каждый оператор switch должен быть полным. То есть, каждое возможное значение рассматриваемого типа должно быть сопоставлено одним из случаев switch. Если не подходит предоставлять случай для каждого возможного значения, вы можете определить случай по умолчанию для обработки любых значений, которые не обрабатываются явно. Этот случай по умолчанию обозначается ключевым словом default и всегда должен стоять последним.
В этом примере используется оператор switch для рассмотрения одной строчной буквы под названием someCharacter:
let someCharacter: Character = "z"
switch someCharacter {
case "a":
print("The first letter of the Latin alphabet")
case "z":
print("The last letter of the Latin alphabet")
default:
print("Some other character")
}
// Prints "The last letter of the Latin alphabet" Первый случай оператора switch соответствует первой букве английского алфавита, a, а его второй случай — последней букве, z. Поскольку оператор switch должен иметь случай для каждой возможной буквы, а не только для каждой буквенной буквы, этот оператор switch использует случай default для сопоставления всех символов, кроме a и z. Это обеспечивает полноту оператора switch.
Как и операторы if, операторы switch также имеют форму выражений:
let anotherCharacter: Character = "a"
let message = switch anotherCharacter {
case "a":
"The first letter of the Latin alphabet"
case "z":
"The last letter of the Latin alphabet"
default:
"Some other character"
}
print(message)
// Prints "The first letter of the Latin alphabet" В этом примере каждый случай в выражении switch содержит значение для message, которое должно быть использовано, когда этот случай совпадает с anotherCharacter. Поскольку оператор switch всегда является полным, всегда есть значение для присваивания.
Как и выражения if, вы можете выбросить ошибку или вызвать функцию, такую как fatalError(_:file:line:), которая никогда не возвращает значение, вместо того, чтобы предоставлять значение для данного случая. Вы можете использовать выражения switch в правой части присваивания, как показано в примере выше, и как значение, которое возвращает функция или замыкание.
Отсутствие неявного перехода
В отличие от операторов switch в C и Objective-C, операторы switch в Swift по умолчанию не пропускают выполнение кода в следующих случаях. Вместо этого, весь оператор switch завершает своё выполнение, как только выполнится первый соответствующий switch случай, без необходимости в явном операторе break. Это делает оператор switch безопаснее и проще в использовании, чем в C, и предотвращает случайное выполнение более одного switch случая.
Примечание: Хотя
breakв Swift не требуется, вы можете использовать операторbreak, чтобы сопоставить и пропустить определённый случай или выйти из сопоставленного случая, прежде чем он завершит своё выполнение. Подробнее см. <doc:ControlFlow#Прерывание-в-операторе-Switch>.
Тело каждого случая должно содержать хотя бы одну исполняемую инструкцию. Недопустимо писать следующий код, так как первый случай пуст:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // Invalid, the case has an empty body
case "A":
print("The letter A")
default:
print("Not the letter A")
}
// This will report a compile-time error. В отличие от оператора switch в C, этот оператор switch не сопоставляет как "a", так и "A". Вместо этого, он выводит ошибку времени компиляции, что case "a": не содержит никаких исполняемых инструкций. Этот подход предотвращает случайное переключение с одного случая на другой и делает код безопаснее и понятнее в своей сути.
Чтобы создать оператор switch с одним случаем, который сопоставляет как "a", так и "A", объедините эти два значения в составной случай, разделив их запятыми.
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
print("The letter A")
default:
print("Not the letter A")
}
// Prints "The letter A" Для повышения читаемости, составной случай также можно записать на нескольких строках. Дополнительную информацию о составных случаях см. в <doc:ControlFlow#Составные-случаи>.
Примечание: Чтобы явно перейти к следующему случаю в конце определённого
switchслучая, используйте ключевое словоfallthrough, как описано в <doc:ControlFlow#Fallthrough>.
Сопоставление интервалов
Значения в switch случаях могут быть проверены на включение в интервал. Этот пример использует числовые интервалы для предоставления естественно-языкового подсчёта для чисел любого размера:
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
naturalCount = "no"
case 1..<5:
naturalCount = "a few"
case 5..<12:
naturalCount = "several"
case 12..<100:
naturalCount = "dozens of"
case 100..<1000:
naturalCount = "hundreds of"
default:
naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// Prints "There are dozens of moons orbiting Saturn." В приведенном выше примере approximateCount оценивается в операторе switch. Каждый case сравнивает это значение с числом или интервалом. Поскольку значение approximateCount попадает между 12 и 100, naturalCount получает значение "dozens of", и выполнение выходит из оператора switch.
Кортежи
Вы можете использовать кортежи для проверки нескольких значений в одном и том же операторе switch. Каждый элемент кортежа может быть проверен на соответствие различным значениям или интервалам значений. В качестве альтернативы, используйте символ нижнего подчеркивания (_), также известный как шаблон подстановки, чтобы сопоставить любое возможное значение.
В примере ниже берётся точка (x, y), выраженная как простой кортеж типа (Int, Int), и она категоризируется на графике, который следует за примером.
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("\(somePoint) is at the origin")
case (_, 0):
print("\(somePoint) is on the x-axis")
case (0, _):
print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
print("\(somePoint) is inside the box")
default:
print("\(somePoint) is outside of the box")
}
// Prints "(1, 1) is inside the box" Оператор switch определяет, находится ли точка в начале координат (0, 0), на красной оси x, на зелёной оси y, внутри синей 4x4 области, центрированной в начале координат, или вне этой области.
В отличие от C, Swift позволяет нескольким switch случаям рассматривать одно и то же значение или значения. Фактически, точка (0, 0) могла бы соответствовать всем четырём случаям в этом примере. Однако, если возможны несколько соответствий, всегда используется первый соответствующий случай. Точка (0, 0) будет соответствовать case (0, 0) первым, и все другие соответствующие случаи будут проигнорированы.
Связывание значений
Случай switch может присваивать соответствующие значения временным константам или переменным для использования в теле случая. Это поведение известно как связывание значений, потому что значения привязываются к временным константам или переменным в теле случая.
В примере ниже берётся точка (x, y), выраженная как кортеж типа (Int, Int), и она категоризируется на графике, который следует за примером:
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
print("on the x-axis with an x value of \(x)")
case (0, let y):
print("on the y-axis with a y value of \(y)")
case let (x, y):
print("somewhere else at (\(x), \(y))")
}
// Prints "on the x-axis with an x value of 2" Оператор switch определяет, находится ли точка на красной оси x, на зелёной оси y или где-то еще (ни на одной оси).
Три switch случая объявляют placeholder константы x и y, которые временно принимают одно или оба значения кортежа из anotherPoint. Первый случай, case (let x, 0), сопоставляет любую точку с y значением 0 и присваивает значение x точки временной константе x. Аналогично, второй случай, case (0, let y), сопоставляет любую точку со значением x как 0 и присваивает значение y точки временной константе y.
После объявления временных констант они могут использоваться в блоке кода случая. Здесь они используются для вывода категоризации точки.
В этом операторе switch нет случая default. Последний случай, case let (x, y), объявляет кортеж из двух placeholder констант, которые могут сопоставиться с любым значением. Поскольку anotherPoint всегда является кортежем из двух значений, этот случай соответствует всем возможным оставшимся значениям, и случай default не нужен, чтобы сделать оператор switch исчерпывающим.
Где
Случай switch может использовать условное выражение where для проверки дополнительных условий.
В примере ниже категоризируется точка (x, y) на следующей диаграмме:
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}
// Prints "(1, -1) is on the line x == -y" Оператор switch определяет, находится ли точка на зелёной диагональной линии, где x == y, на фиолетовой диагональной линии, где x == -y, или ни на какой.
Три switch случая объявляют placeholder константы x и y, которые временно принимают два значения кортежа из yetAnotherPoint. Эти константы используются как часть условного выражения where, чтобы создать динамический фильтр. Случай switch соответствует текущему значению point только в том случае, если условие условного выражения where вычисляется как true для этого значения.
Как и в предыдущем примере, заключительный случай соответствует всем возможным остальным значениям, поэтому случай default не нужен, чтобы сделать оператор switch исчерпывающим.
Составные случаи
Несколько случаев switch, которые имеют одинаковое тело, могут быть объединены, написав несколько шаблонов после case, разделяя каждый шаблон запятой. Шаблоны можно записать на нескольких строках, если список большой. Например:
let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("\(someCharacter) is a consonant")
default:
print("\(someCharacter) isn't a vowel or a consonant")
}
// Prints "e is a vowel" Первый случай оператора switch соответствует всем пяти гласным буквам в английском языке строчными. Аналогично, второй случай соответствует всем строчным английским согласным. Наконец, случай default соответствует любому другому символу.
Составные случаи также могут включать связывание значений. Все шаблоны составного случая должны содержать один и тот же набор связывания значений, и каждое связывание должно получать значение одного и того же типа от всех шаблонов в составном случае. Это гарантирует, что независимо от того, какая часть составного случая соответствовала, код в теле случая всегда может получить доступ к значению для связываний, и это значение всегда имеет тот же тип.
let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
print("On an axis, \(distance) from the origin")
default:
print("Not on an axis")
}
// Prints "On an axis, 9 from the origin" Приведённый выше case имеет два шаблона: (let distance, 0) соответствует точкам на оси x, и (0, let distance) соответствует точкам на оси y. Оба шаблона включают связывание для distance, и distance — целое число в обоих шаблонах — что означает, что код в теле оператора case всегда может получить доступ к значению для distance.
Операторы передачи управления
Операторы передачи управления изменяют порядок выполнения вашего кода, передавая управление из одной части кода в другую. В Swift есть пять операторов передачи управления:
continuebreakfallthroughreturnthrow
Операторы continue, break и fallthrough описаны ниже. Оператор return описан в Функции, а оператор throw описан в <doc:ErrorHandling#Распространение-ошибок-с-помощью-функций-с-броском>.
Продолжить
Оператор continue сообщает циклу о прекращении текущей итерации и переходе к следующей итерации. Он означает «я закончил с текущей итерацией цикла», но не выходит из цикла полностью.
В следующем примере удаляются все гласные и пробелы из строчной строки, чтобы создать загадочную фразу:
let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
for character in puzzleInput {
if charactersToRemove.contains(character) {
continue
}
puzzleOutput.append(character)
}
print(puzzleOutput)
// Prints "grtmndsthnklk" В приведенном выше коде вызывается ключевое слово continue всякий раз, когда он соответствует гласной или пробелу, что приводит к немедленному завершению текущей итерации цикла и переходу к началу следующей итерации.
Прервать
Оператор break немедленно завершает выполнение всего оператора управления. Оператор break можно использовать внутри операторов switch или циклов, когда требуется преждевременное завершение выполнения оператора switch или цикла.
Прервать в операторе цикла
При использовании внутри оператора цикла, break немедленно завершает выполнение цикла и переносит управление на код после закрывающей фигурной скобки цикла (}). Дальнейший код текущей итерации цикла не выполняется, и не запускаются дальнейшие итерации цикла.
Прервать в операторе switch
При использовании внутри оператора switch, break вызывает немедленное завершение выполнения оператора switch и переносит управление на код после закрывающей фигурной скобки оператора switch (}).
Это поведение может использоваться для сопоставления и игнорирования одного или нескольких случаев в операторе switch. Поскольку оператор switch оператор switch в Swift является исчерпывающим и не допускает пустых случаев, иногда необходимо преднамеренно сопоставлять и игнорировать случай, чтобы сделать ваши намерения ясными. Для этого напишите оператор break как всё тело случая, который вы хотите пропустить. Когда этот случай сопоставляется с оператором switch, оператор break внутри случая немедленно завершает выполнение оператора switch.
Примечание: случай
switch, содержащий только комментарий, сообщается как ошибка времени компиляции. Комментарии не являются операторами и не приводят к игнорированию случаяswitch. Для игнорирования случаяswitchвсегда используйте операторbreak.
Следующий пример переключается на значение Character и определяет, представляет ли оно символ числа в одном из четырёх языков. Для краткости, несколько значений покрываются в одном случае switch.
let numberSymbol: Character = "三" // Chinese symbol for the number 3
var possibleIntegerValue: Int?
switch numberSymbol {
case "1", "١", "一", "๑":
possibleIntegerValue = 1
case "2", "٢", "二", "๒":
possibleIntegerValue = 2
case "3", "٣", "三", "๓":
possibleIntegerValue = 3
case "4", "٤", "四", "๔":
possibleIntegerValue = 4
default:
break
}
if let integerValue = possibleIntegerValue {
print("The integer value of \(numberSymbol) is \(integerValue).")
} else {
print("An integer value couldn't be found for \(numberSymbol).")
}
// Prints "The integer value of 三 is 3." Этот пример проверяет numberSymbol, чтобы определить, является ли он латинским, арабским, китайским или тайским символом для чисел 1 по 4. Если совпадение найдено, один из случаев оператора switch устанавливает необязательную переменную Int? с именем possibleIntegerValue в соответствующее целое значение.
После завершения выполнения оператора switch пример использует необязательную привязку, чтобы определить, было ли найдено значение. Переменная possibleIntegerValue имеет неявное начальное значение nil благодаря своему типу «необязательно», поэтому необязательная привязка будет успешной только в том случае, если possibleIntegerValue было установлено в фактическое значение одним из первых четырёх случаев оператора switch.
Поскольку в примере выше непрактично перечислить каждое возможное значение Character, случай default обрабатывает любые символы, которые не соответствуют условиям. Этот случай default не требует выполнения каких-либо действий, поэтому в нём содержится единственный оператор break. Как только совпадает случай default, оператор break завершает выполнение оператора switch, а выполнение кода продолжается с оператора if let.
Проход
В Swift операторы switch не «проваливаются» в следующий случай. То есть, весь оператор switch завершает выполнение сразу после завершения выполнения первого соответствующего случая. В отличие от этого, C требует вставки явного оператора break в конце каждого случая switch, чтобы предотвратить проход. Избежание автоматического прохода означает, что операторы switch в Swift намного более лаконичны и предсказуемы, чем их аналоги в C, и поэтому они предотвращают случайное выполнение нескольких случаев switch.
Если вам требуется поведение прохода в стиле C, вы можете включить это поведение на основе каждого случая, используя ключевое слово fallthrough. В примере ниже используется fallthrough для создания текстового описания числа.
let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
description += " a prime number, and also"
fallthrough
default:
description += " an integer."
}
print(description)
// Prints "The number 5 is a prime number, and also an integer." Этот пример объявляет новую переменную String с именем description и присваивает ей начальное значение. Затем функция рассматривает значение integerToDescribe, используя оператор switch. Если значение integerToDescribe является одним из простых чисел в списке, функция добавляет текст в конец description, чтобы отметить, что число является простым. Затем она использует ключевое слово fallthrough, чтобы «перейти» к случаю default также. Случай default добавляет дополнительный текст в конец описания, и выполнение оператора switch завершено.
Если значение integerToDescribe не входит в список известных простых чисел, оно вообще не соответствует первому случаю switch. Поскольку нет других конкретных случаев, integerToDescribe соответствует случаю default.
После завершения выполнения оператора switch описание числа печатается с помощью функции print(_:separator:terminator:). В этом примере число 5 правильно идентифицируется как простое число.
Примечание: ключевое слово
fallthroughне проверяет условия случаяswitch, в который оно вызывает выполнение кода. Ключевое словоfallthroughпросто заставляет выполнение кода перейти непосредственно к операторам внутри следующего блока случая (или блока случаяdefault), как и в стандартном поведении оператораswitchC.
Операторные метки
В Swift вы можете вкладывать циклы и условные операторы в другие циклы и условные операторы, чтобы создавать сложные структуры управления потоком. Однако циклы и условные операторы оба могут использовать оператор break, чтобы преждевременно завершить своё выполнение. Поэтому иногда полезно явно указать, какой цикл или условный оператор вы хотите прервать с помощью оператора break. Аналогично, если у вас несколько вложенных циклов, может быть полезно явно указать, на какой цикл должен повлиять оператор continue.
Для достижения этих целей вы можете пометить оператор цикла или условный оператор меткой оператора. С условным оператором вы можете использовать метку оператора с оператором break, чтобы прервать выполнение помеченного оператора. С оператором цикла вы можете использовать метку оператора с операторами break или continue, чтобы прервать или продолжить выполнение помеченного оператора.
Помеченный оператор обозначается размещением метки в той же строке, что и ключевое слово-инициатор оператора, за которым следует двоеточие. Вот пример такого синтаксиса для цикла while, хотя принцип тот же для всех циклов и операторов switch:
<#label name#>: while <#condition#> {
<#statements#>
} В следующем примере используются операторы break и continue с помеченным циклом while для адаптированной версии игры «Змеи и лестницы», которую вы видели ранее в этой главе. На этот раз в игре есть дополнительное правило:
- Чтобы выиграть, вы должны точно попасть на клетку 25.
Если определённый бросок костей переведёт вас за клетку 25, вам нужно бросить снова, пока вы не бросите ровно то число, которое нужно для попадания на клетку 25.
Игровое поле такое же, как и раньше.
Значения finalSquare, board, square и diceRoll инициализируются так же, как и раньше:
let finalSquare = 25 var board = [Int](repeating: 0, count: finalSquare + 1) board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 var square = 0 var diceRoll = 0
Эта версия игры использует цикл while и оператор switch для реализации логики игры. Цикл while имеет метку оператора под названием gameLoop, чтобы указать, что это основной цикл игры «Змеи и лестницы».
Условие цикла while равно while square != finalSquare, чтобы отразить необходимость точного попадания на клетку 25.
gameLoop: while square != finalSquare {
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// diceRoll will move us to the final square, so the game is over
break gameLoop
case let newSquare where newSquare > finalSquare:
// diceRoll will move us beyond the final square, so roll again
continue gameLoop
default:
// this is a valid move, so find out its effect
square += diceRoll
square += board[square]
}
}
print("Game over!") Кости бросаются в начале каждого цикла. Вместо того, чтобы немедленно передвигать игрока, цикл использует оператор switch, чтобы рассмотреть результат перемещения и определить, разрешено ли это перемещение:
- Если бросок костей переместит игрока на конечную клетку, игра окончена. Оператор
break gameLoopпередаёт управление первой строке кода за пределами циклаwhile, что завершает игру. - Если бросок костей переместит игрока за конечную клетку, перемещение недействительно, и игроку нужно бросить снова. Оператор
continue gameLoopзавершает текущую итерацию циклаwhileи начинает следующую итерацию цикла. - Во всех остальных случаях бросок костей является допустимым перемещением. Игрок передвигает вперёд на
diceRollклеток, и логика игры проверяет наличие змей и лестниц. Затем цикл завершается, и управление возвращается к условиюwhile, чтобы решить, требуется ли ещё один ход.
Примечание: если оператор
breakвыше не использовал меткуgameLoop, он бы вышел из оператораswitch, а не из оператораwhile. Использование меткиgameLoopделает ясным, какой оператор управления следует завершить.Строго говоря, использование метки
gameLoopпри вызовеcontinue gameLoopдля перехода к следующей итерации цикла необязательно. В игре только один цикл, и поэтому нет неоднозначности относительно того, на какой цикл повлияет операторcontinue. Однако использование меткиgameLoopс операторомcontinueне принесёт вреда. Это согласуется с использованием метки вместе с операторомbreakи способствует более чёткому пониманию логики игры.
Преждевременный выход
Оператор guard, подобно оператору if, выполняет операторы в зависимости от булевого значения выражения. Вы используете оператор guard, чтобы потребовать, чтобы условие было истинным, чтобы код после оператора guard был выполнен. В отличие от оператора if, оператор guard всегда имеет ветвь else — код внутри ветви else выполняется, если условие ложно.
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)!")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
greet(person: ["name": "John"])
// Prints "Hello John!"
// Prints "I hope the weather is nice near you."
greet(person: ["name": "Jane", "location": "Cupertino"])
// Prints "Hello Jane!"
// Prints "I hope the weather is nice in Cupertino." Если условие оператора guard выполняется, выполнение кода продолжается после закрывающей фигурной скобки оператора guard. Любые переменные или константы, которым были присвоены значения с помощью необязательной привязки в рамках условия, доступны для остальной части блока кода, в котором находится оператор guard.
Если это условие не выполняется, выполняется код внутри ветви else. Эта ветвь должна передать управление, чтобы выйти из блока кода, в котором находится оператор guard. Она может сделать это с помощью оператора передачи управления, такого как return, break, continue или throw, или она может вызвать функцию или метод, который не возвращает значение, такой как fatalError(_:file:line:).
Использование оператора guard для требований улучшает читаемость кода по сравнению с выполнением той же проверки с помощью оператора if. Оно позволяет вам написать код, который обычно выполняется, не заключая его в блок else, и позволяет вам разместить код, обрабатывающий нарушение требования, рядом с самим требованием.
Отложенные действия
В отличие от конструкций управления потоком, таких как if и while, которые позволяют управлять тем, выполняется ли часть вашего кода или сколько раз она выполняется, defer управляет временем выполнения фрагмента кода. Вы используете блок defer, чтобы написать код, который будет выполнен позже, когда ваша программа достигнет конца текущего области видимости. Например:
var score = 1
if score < 10 {
defer {
print(score)
}
score += 5
}
// Prints "6" В приведённом выше примере код внутри блока defer выполняется перед выходом из тела оператора if. Сначала выполняется код оператора if, который увеличивает значение score на пять. Затем, перед выходом из области действия оператора if, выполняется отложенный код, который выводит значение score.
Код внутри блока defer всегда выполняется, независимо от того, как программа выходит из этой области. Это включает код, такой как ранний выход из функции, выход из цикла for или возникновение ошибки. Это поведение делает defer полезным для операций, где необходимо гарантировать выполнение пары действий — например, ручное выделение и освобождение памяти, открытие и закрытие низкоуровневых дескрипторов файлов и начало и завершение транзакций в базе данных — потому что вы можете записать оба действия рядом друг с другом в вашем коде. Например, следующий код даёт временный бонус к результату, добавляя и вычитая 100 внутри фрагмента кода:
var score = 3
if score < 100 {
score += 100
defer {
score -= 100
}
// Other code that uses the score with its bonus goes here.
print(score)
}
// Prints "103" Если вы запишете более одного блока defer в одной области действия, первым будет выполняться последний из указанных блоков.
if score < 10 {
defer {
print(score)
}
defer {
print("The score is:")
}
score += 5
}
// Prints "The score is:"
// Prints "6" Если выполнение программы прекращается — например, из-за ошибки выполнения или сбоя — отложенный код не выполняется. Однако отложенный код выполняется после возникновения ошибки; для получения информации об использовании defer с обработкой ошибок см. <doc:ErrorHandling#Specifying-Cleanup-Actions>.
Проверка доступности API
Swift имеет встроенную поддержку проверки доступности API, которая гарантирует, что вы не используете случайно недоступные API на данном целевом устройстве.
Компилятор использует информацию о доступности в SDK для проверки того, что все API, используемые в вашем коде, доступны на целевом устройстве, указанном в вашем проекте. Swift сообщает об ошибке во время компиляции, если вы пытаетесь использовать API, который недоступен.
Вы используете условие доступности в операторе if или guard для условного выполнения блока кода в зависимости от того, доступны ли API, которые вы хотите использовать, во время выполнения. Компилятор использует информацию из условия доступности, когда проверяет, что API в этом блоке кода доступны.
if #available(iOS 10, macOS 10.12, *) {
// Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
// Fall back to earlier iOS and macOS APIs
} Условие доступности выше указывает, что в iOS тело оператора if выполняется только в iOS 10 и более поздних версиях; в macOS — только в macOS 10.12 и более поздних версиях. Последний аргумент, *, является обязательным и указывает, что на любой другой платформе тело оператора if выполняется на минимальной целевой версии, указанной для вашего проекта.
В общем виде условие доступности принимает список имён платформ и версий. Вы используете имена платформ, такие как iOS, macOS, watchOS, tvOS и visionOS — для получения полного списка см. <doc:Attributes#Declaration-Attributes>. В дополнение к указанию основных номеров версии, таких как iOS 8 или macOS 10.10, вы можете указывать и второстепенные номера версии, такие как iOS 11.2.6 и macOS 10.13.3.
if #available(<#platform name#> <#version#>, <#...#>, *) {
<#statements to execute if the APIs are available#>
} else {
<#fallback statements to execute if the APIs are unavailable#>
} При использовании условия доступности с оператором guard оно уточняет информацию о доступности, используемую для остальной части кода в данном блоке.
@available(macOS 10.12, *)
struct ColorPreference {
var bestColor = "blue"
}
func chooseBestColor() -> String {
guard #available(macOS 10.12, *) else {
return "gray"
}
let colors = ColorPreference()
return colors.bestColor
} В приведённом выше примере структура ColorPreference требует macOS 10.12 или более поздних версий. Функция chooseBestColor() начинается с условия доступности. Если версия платформы слишком старая для использования ColorPreference, она переходит к альтернативному поведению, которое всегда доступно. После оператора guard вы можете использовать API, которые требуют macOS 10.12 или более поздних версий.
В дополнение к #available, Swift также поддерживает обратную проверку с использованием условия недоступности. Например, следующие две проверки выполняют одно и то же:
if #available(iOS 10, *) {
} else {
// Fallback code
}
if #unavailable(iOS 10) {
// Fallback code
} Использование формы #unavailable помогает сделать ваш код более читабельным, когда проверка содержит только альтернативный код.
This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception