Управление потоком

В Swift есть все знакомые нам операторы управления потоком из C-подобных языков. К ним относятся: циклы for-in и while для многократного выполнения задач, инструкции if, guard и switch для выполнения различных ветвлений кода в зависимости от определенных условий, а также такие инструкции, как break и continue для перемещения потока выполнения в другую точку вашего кода.

17 ноября 2022

 

Swift предоставляет цикл for-in, который упрощает итерацию по массивам, словарям, диапазонам, строкам и другим последовательностям.

В Swift инструкция switch также намного мощнее, чем его аналог из языка C. В Swift не происходит проваливания к следующему кейсу, что позволяет избежать распространенную в C ошибку, связанную с пропуском оператора break. Кейсы могут сопоставлять различные типы шаблонов, включая сопоставление диапазонов, кортежей, а также выполнять приведение к определенному типу. Совпавшие значения в кейсе оператора 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!

Таким же образом вы можете производить итерацию по словарю, чтобы получить доступ к его паре ключ-значение. Когда происходит итерация по словарю, каждый его элемент возвращается как кортеж (ключ, значение). Вы можете разложить члены кортежа на отдельные константы для того, чтобы использовать их в теле цикла for-in. Здесь ключи словаря распадаются в константу animalName, а его значения - в константу legCount:


let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
    print("\(animalName)s have \(legCount) legs")
}
// ants have 6 legs
// cats have 4 legs
// spiders have 8 legs

Содержимое словаря по сути своей не является упорядоченным, поэтому и извлекаемые из него значения во время итерации тоже могут быть не упорядочены. Более подробно о массивах и словарях смотрите в главе Типы Коллекций.

Вы так же можете использовать for-in с числовыми диапазонами. Следующий пример напечатает несколько первых значений таблицы умножения на 5:


for index in 1...5 {
    print("\(index) умножить на 5 будет \(index * 5)")
}
// 1 умножить на 5 будет 5
// 2 умножить на 5 будет 10
// 3 умножить на 5 будет 15
// 4 умножить на 5 будет 20
// 5 умножить на 5 будет 25

Коллекция элементов, по которой происходит итерация, является закрытым диапазоном чисел от 1 до 5 включительно, так как используется оператор закрытого диапазона(...). Значение index устанавливается в первое число из диапазона (1), и выражение внутри цикла выполняются. В данном случае, цикл содержит только одно выражение, которое печатает запись из таблицы умножения на пять для текущего значения index. После того как выражение выполнено, значение index обновляется до следующего значения диапазона (2), и функция print(_:separator:terminator:) снова вызывается. Этот процесс будет продолжаться до тех пор, пока не будет достигнут конец диапазона.

В примере выше index является константой, значение которой автоматически устанавливается в начале каждой итерации цикла. Как таковую, ее не нужно объявлять перед использованием. Ее объявление неявно происходит в объявлении цикла, без необходимости использования зарезервированного слова let.

Если Вам не нужно каждое значение из диапазона, то вы можете игнорировать их, используя символ подчёркивания вместо имени переменной:


let base = 3
let power = 10
var answer = 1
for _ in 1...power {
    answer *= base
}
print("\(base) в степени \(power) равно \(answer)")
// Выведет "3 в степени 10 равно 59049"

В этом примере вычисляется значение одного числа возведенное в степень другим (в данном случае 3 в степени 10). Начальное значение 1 (то есть 3 в степени 0) умножается на 3 десять раз, используя закрытый диапазон значений, который начинается с 1, и заканчивается 10. В данном случае нет необходимости знать значения счётчика во время каждой итерации цикла - он просто должен выполниться необходимое количество раз. Символ подчёркивания "_" (который используется вместо переменной цикла) игнорирует ее отдельные значения и не предоставляет доступ к текущему значению во время каждой итерации цикла.

В некоторых случаях вы можете не захотеть использовать замкнутый диапазон, который включает в себя оба конечных значения диапазона. Предположим, что вы хотите отрисовать минутные значения в виде черточек на часах. Вы будете рисовать 60 таких отметок, начиная с 0 минуты. Используйте полузамкнутый диапазон ( ..<), чтобы включить нижнюю границу, но не верхнюю. Для более подробного изучения диапазонов вам нужно перейти в главу Операторы диапазона.


let minutes = 60
for tickMark in 0..

Некоторые пользователи, возможно, захотят иметь поменьше минутных делений, и, предположим, они захотят иметь отметки на циферблате только на каждые 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)
}

Циклы While

Цикл while выполняет набор инструкций до тех пор, пока его условие не станет false. Этот вид циклов лучше всего использовать в тех случаях, когда количество итераций до первого входа в цикл неизвестно. Swift предлагает два вида циклов while:

  • while - вычисляет условие выполнения в начале каждой итерации цикла.
  • repeat-while - вычисляет условие выполнения в конце каждой итерации цикла.

While

Цикл while начинается с вычисления условия. Если условие истинно, то инструкции в теле цикла будут выполняться до тех пор, пока оно не станет ложным.

Общий вид цикла while выглядит следующим образом:

  1. while условие {
  2. инструкции
  3. }

В этом примере показана простая игра Змеи и Лестницы (также известная, как Горы и Лестницы):

Игра проходит по следующим правилам:

  • Доска разделена на 25 квадратов и цель состоит в том, чтобы стать на 25-ый квадрат или за его пределами.
  • Игрок начинает с "нулевого квадрата", который расположен в самом левом нижнем углу доски.
  • В начале каждого хода вы бросаете игральную кость и перемещаетесь на то число шагов, которое выпало после броска, в направлении, которое указывает пунктирная стрелка.
  • Если ваш ход заканчивается на основании лестницы, то вы поднимаетесь по ней вверх.
  • Если ваш ход заканчивается на голове змеи, то вы спускаетесь вниз по хвосту змеи.

Игровая доска в примере представлена массивом значений типа Int. Его размер хранится в константе finalSquare, которая используется как для инициализации массива, так и для проверки условия победы. Т.к. игроки стартуют за пределами доски, с "нулевого квадрата", игровое поле инициализируется 26-ю, а не 25-ю целочисленными нулевыми значениями(каждое с индексом от 0 до 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 типа Int (разница между 3 и 11). Для того чтобы уточнить формулировку игрового поля, оператор унарного плюса (+i) уравновешивает оператор унарного минуса (-i), а числам ниже 10 приписаны нули. (В этих двух стилистических надстройках нет прямой необходимости, но они делают код более читаемым).


var square = 0
var diceRoll = 0
while square < finalSquare {
  // бросок кубика
  diceRoll += 1
  if diceRoll == 7 { diceRoll = 1 }
  // начать ходить на выпавшее количество шагов
  square += diceRoll
  if square < board.count {
    // если мы все еще на поле, идти вверх или вниз по змеям или лестницам
    square += board[square]
  }
}
print("Game over!")

Данный пример использует самый простой подход к реализации броска кубика. Вместо использования генератора случайных чисел, значение diceRoll начинается с 0. Каждую итерацию цикла переменная diceRoll увеличивается на 1 с помощью инфиксного оператора (+= 1), после чего проверяется не стало ли её значение слишком большим. Возвращаемое значение += diceRoll равно значению переменной diceRoll после её инкрементирования. Когда это значение становится равным 7, оно сбрасывается на 1. В итоге мы получаем последовательность значений diceRoll, которая всегда будет выглядеть следующим образом: 1, 2, 3, 4, 5, 6, 1, 2 и так далее.

После броска кубика игрок перемещается вперед на количество клеток, равное значению переменной diceRoll. Возможен случай, когда бросок кубика может переместить игрока за пределы квадрата 25. В таком случае игра заканчивается. Для того чтобы справиться с таким сценарием, код проверяет что значение square меньше чем свойство count массива board перед прибавлением значения, хранящегося в board[square] к текущему значению square для перемещения игрока вверх или вниз по змеям или лестницам.

Заметка

Если бы этой проверки не было, могла бы произойти попытка обращения к значению board[square], находящемуся за границами массива board, что привело бы к вызову ошибки. Если square равно 26, код попытается проверить значение board[26], которое выходит за границы массива.

Текущая итерация цикла заканчивается, после чего проверяется условие цикла, для того чтобы понять нужно ли переходить к следующей итерации. Если игрок переместился на квадрат 25 или за его пределы, значение условия будет вычислено как false и игра закончится.

В данном случае использование while является наиболее подходящим, так как продолжительность игры неизвестна перед началом цикла. Цикл просто исполняется до тех пор, пока не будет выполнено конкретное условие.

Цикл repeat-while

Другой вариант цикла while, известный как цикл repeat-while, выполняет одну итерацию до того, как происходит проверка условия. Затем цикл продолжает повторяться до тех пор, пока условие не станет false.

Заметка

Цикл repeat-while в Swift аналогичен циклу do-while в других языках.

Общий вид цикла repeat-while выглядит следующим образом:

  1. repeat {
  2. инструкции
  3. } while условие

Ниже снова представлен пример игры Змеи и Лестницы , написанный с использованием цикла repeat-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. Таким образом невозможно победить в игре, переместившись вверх по лестнице. Следовательно, такая проверка в самом начале цикла является абсолютно безопасной.

В начале игры игрок находится на квадрате 0. board[0] всегда равняется 0 и не оказывает никакого влияния:


repeat {
  // идти вверх или вниз по змеям или лестницам
  square += board[square]
  // бросить кубик
  diceRoll += 1
  if diceRoll == 7 { diceRoll = 1 }
  // начать ходить на выпавшее количество шагов
  square += diceRoll
} while square < finalSquare
print("Game over!")

После проверки на наличие змей и лестниц происходит бросок кубика и игрок продвигается вперед на количество квадратов, равное diceRoll. После этого текущая итерация цикла заканчивается.

Условие цикла (while square < finalSquare) такое же, как раньше, но в этот раз оно не вычисляется до окончания первого запуска цикла. Структура цикла repeat-while лучше подходит для этой игры, чем цикл while в предыдущем примере. В цикле repeat-while выше square += board[square] всегда выполняется сразу, в то время как в цикле while происходит проверка того, что square все еще находится на поле. Такой принцип работы цикла repeat-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.")
}
// Выведет "It's very cold. Consider wearing a scarf.

В приведенном примере проверяется значение температуры, которая может быть ниже или 32 (0 по Цельсию) градусов по Фаренгейту либо равна или выше. Если она ниже, то выведется сообщение. В противном случае никакого сообщения не будет, и код продолжит свое выполнение после закрывающей фигурной скобки инструкции if.

Инструкция if может предусматривать еще один дополнительный набор инструкций в ветке известной как оговорка else, которая нужна в случае, если условие 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.")
}
// Выведет "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.")
}
// Выведет "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.")
}

В этом примере температура ни высокая, ни низкая, и вообще она не соответствует ни одному условию, так что никакого сообщения мы не увидим.

Инструкция switch

Инструкция switch подразумевает наличие какого-то значения, которое сравнивается с несколькими возможными шаблонами. После того как значение совпало с каким-либо шаблоном, выполняется код, соответствующий ответвлению этого шаблона, и больше сравнений уже не происходит. Switch представляет собой альтернативу инструкции if, отвечающей нескольким потенциальным значениям.

В самой простой форме в инструкции switch значение сравнивается с одним или более значений того же типа:


switch значение для сопоставления {
     case значение 1: 
          инструкция для значения 1
     case значение 2, значение 3:
          инструкция для значения 2 или значения 3
     default: 
          инструкция, если совпадений с шаблонами не найдено
}

Каждая инструкция switch состоит из нескольких возможных случаев или cases, каждый из которых начинается с ключевого слова case. Помимо сравнения с конкретными значениями, Swift предлагает еще несколько опций для каждого случая для создания более сложных шаблонных сравнений. Об этих опциях мы поговорим далее в этой главе.

Тела каждого отдельного блока case в switch - это отдельная ветка исполнительного кода, что делает switch похожим на инструкцию if. Инструкция switch определяет какое ответвление должно быть выбрано. Это известно как переключение на значение, которое в настоящее время рассматривается.

Каждая инструкция switch должна быть исчерпывающей. То есть это значит, что каждое значение обязательно должно находить совпадение с шаблоном в каком-либо случае (case). Если неудобно вписывать все возможные варианты случаев, то вы можете определить случай по умолчанию, который включает в себя все значения, которые не были включены в остальные случаи. Такой случай по умолчанию называется default, и он всегда идет после всех остальных случаев.

В следующем примере switch рассматривает единичный символ в нижнем регистре, который называется someCharacter:


let someCharacter: Character = "z"
switch someCharacter {
case "a":
    print("The first letter of the alphabet")
case "z":
    print("The last letter of the alphabet")
default:
    print("Some other character")
}
// Выведет "The last letter of the alphabet"

Первый кейс инструкции switch соответствует первой букве английского алфавита - a, второй кейс соответствует последней букве - z. Так как switch должна иметь кейс для каждого возможного символа, а не просто для каждой буквы алфавита, то в инструкции switch предусмотрен дефолтный кейс, который звучит как default, в который входят все символы кроме и z. Как раз это условие гарантирует, что инструкция switch будет исчерпывающей.

Отсутствие case-провалов

Большое отличие инструкции switch в языке Swift от инструкции switch в C и Objective-C составляет отсутствие провалов через условия. Вместо этого инструкция switch прекращает выполнение после нахождения первого соответствия с case и выполнения соответствующего кода в ветке, без необходимости явного вызова break. Это делает инструкцию switch более безопасным и простым для использования, чем в C, и исключает исполнение кода более чем одного случая.

Заметка

Хотя break не требуются в Swift, вы все равно можете его использовать для соответствия и для игнорирования конкретного случая или просто для выхода из конкретного случая, еще до того, как исполнится код. Более детально можно прочитать в разделе Оператор Break в инструкции Switch.

Тело каждого случая должно включать в себя хотя бы одно исполняемое выражение. Код не будет исполнен и выдаст ошибку компиляции, если написать его следующим образом:


let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a":  // ошибка, так как кейс имеет пустое тело
case "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// ошибка компиляции

В отличии от инструкции switch в языке C, switch в Swift не соответствует ни "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")
}
// Выведет "The letter A"

Для того, чтобы составной кейс удобно было читать, он может быть написан несколькими строчками. Более подробно далее в "Составные Кейсы".

Заметка

Для того, чтобы у вас появилась возможность проваливаться в конце конкретного switch кейса, используйте ключевое слово fallthrough, как описано в следующих главах.

Соответствие диапазону

Значения в кейсах switch могут быть проверены на их вхождение в диапазон. Пример ниже использует целочисленные диапазоны для описания любых значений художественным языком:


let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var 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).")
// Выводит "There are dozens of moons orbiting Saturn."

В приведенном выше примере, approximateCount оценивается в инструкции switch. Каждый кейс сравнивает это значение с числом или интервалом. Поскольку значение 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")
}
// Выведет "(1, 1) is inside the box"

Инструкции switch определяет: находится ли точка в начале отсчета (0,0), на красной оси x, на оранжевой оси y, внутри синего квадрата 4х4, в котором точка отсчета находится в центре или находится вне этого квадрата.

В отличии от C, инструкция switch в Swift позволяет множественное совпадение или пересечение значений нескольких случаев. Это факт, что точка (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))")
}
// Выведет "on the x-axis with an x value of 2

Инструкция switch определяет лежит ли точка на красной оси x или оранжевой оси y, а может быть она не будет ни на одной из осей.

Три случая в инструкции switch объявляют константы x, y, которым временно присваиваются значения одного или обоих элементов из кортежа anotherPoint. В первом кейсе (let x, 0): подойдет любая точка со значением y равным 0, а в константу x запишется значение координаты x нашей точки. Аналогично и во втором случае, когда case (0, let y), этот кейс включает все точки при значении их координаты x равной 0, и происходит присваивание значения координаты y в временную константу y.

Объявленная константа может быть использована внутри блока кейса. Здесь мы их используем как сокращенный вариант для вывода сообщения с помощью функции print.

Заметьте, что инструкция switch не имеет случая default. Последний кейс let (x, y) объявляет кортеж двух констант плейсхолдеров, которые могут соответствовать абсолютно любому значению. Так как anotherPoint это кортеж с двумя значениями, этот кейс подходит под все возможные оставшиеся значения, и кейс default уже не нужен, так как инструкция switch исчерпывающая.

Where

В кейсе инструкции 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")
}
// Выведет "(1, -1) is on the line x == -y"

Инструкция switch определяет лежит ли точка на зеленой диагонали, где x == y, или фиолетовой диагонали, где x == -y, или ни на одной и ни на другой.

Три кейса объявляют константы 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) is not a vowel or a consonant")
}
// Напечатает "e is a vowel"

В примере выше первый кейс инструкции switch включает в себя сразу все пять гласных английского языка записанных в нижнем регистре. Аналогично второй кейс содержит все согласные английского языка в нижнем регистре. И, наконец, дефолтный кейс содержит в себе все оставшиеся символы.

Составные кейсы так же могут включать в себя привязку значения. Все шаблоны составных кейсов должны включать в себя тот же самый набор значений и каждая связка должна быть одного и того же типа из всех шаблонов составного кейса. Это гарантирует тот факт, что независимо от того, какая часть составного кейса совпала со сравниваемым значением, код в теле всегда получит доступ к значению  привязки и это значение всегда будет одного типа.


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")
}
// Напечатает "On an axis, 9 from the origin"

Кейс выше имеет два шаблона: (let distance, 0), который соответсвует любой точке на оси x, и (0, let distance), что соответствует точке на оси y. И тот и другой шаблон включают в себя привязку для distance и distance является целочисленным значением для двух этих шаблонов, что значит, что код внутри тела кейса всегда будет иметь доступ к значению distance.

 

Операторы передачи управления

Операторы передачи управления меняют последовательность исполнения вашего кода, передавая управление от одного фрагмента кода другому. В Swift есть пять операторов передачи управления:

  • continue
  • break
  • fallthrough
  • return
  • throw

Операторы continue, break, fallthrough будут описаны в следующих главах, оператор return будет описан в главе Функции, а оператор throw будет описан в Передача ошибки с помощью генерирующей функции.

Оператор Continue

Оператор 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
    } else {
        puzzleOutput.append(character)
    }
}
print(puzzleOutput)
// Выведет "grtmndsthnklk"

Пример выше вызывает оператор continue, когда он находит соответствие с гласными звуками или пробелом, вызывая тем самым прекращение текущей итерации и начало новой.

Оператор Break

Оператор break останавливает выполнение всей инструкции управления потоком. Оператор break может быть использован внутри инструкции switch или внутри цикла, когда вы хотите остановить дальнейшее выполнение switch или цикла раньше, чем он должен закончиться сам по себе.

Оператор Break в цикле

Когда оператор break используется внутри цикла, то он немедленно прекращает работу цикла, и выполнение кода продолжается с первой строки после закрывающей скобки цикла (}). Никакой последующий код из текущей итерации цикла выполняться не будет, и никакие дальнейшие итерации цикла не будут запускаться.

Оператор Break в инструкции Switch

Когда оператор break используется внутри инструкции switch, то он прекращает исполнение кода конкретного случая и перекидывает исполнение на первую строку после закрывающей скобки (}) инструкции switch.

Так же оператор break может использоваться для сопоставления или игнорирования кейсов в инструкции switch. Так как инструкция switch исчерпывающая и не допускает пустых кейсов, то иногда бывает необходимо умышленно соответствовать или игнорировать кейсы для того, чтобы сделать ваши намерения ясными. Вы делаете это, когда пишите слово break в теле кейса, который вы хотите пропустить. Когда этот кейс попадает под сравнение, то break сразу завершает работу всей инструкции switch.

Заметка

Кейс в инструкции switch, который содержит только комментарий, при компиляции выдаст ошибку компиляции. Комментарии - это не утверждения, и они не дают возможности игнорировать кейсы. Если вы хотите игнорировать кейс switch, используйте break.

Следующий пример переключается на символьные значение Character и определяет, является ли символ целым числом на одном из четырех языков. Несколько языков включены в каждый кейс для краткости:


let numberSymbol: Character = "三"  // Цифра 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 could not be found for \(numberSymbol).")
}
// Выведет "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.

Оператор Fallthrough

Инструкция switch в Swift не проваливается из каждого кейса в следующий. Напротив, как только находится соответствие с первым кейсом, так сразу и прекращается работа всей инструкции. А в языке C, работа инструкции switch немного сложнее, так как требует явного прекращения работы при нахождении соответствия словом break в конце кейса, в противном случае при соответствии мы провалимся в следующий случай и так далее пока не встретим слово break. Избежание провалов значит что инструкция switch в Swift более краткая и предсказуемая, чем она же в C, так как она предотвращает срабатывание нескольких кейсов по ошибке.

Если вам по какой-то причине нужно аналогичное проваливание как в 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)
// Выведет "The number 5 is a prime number, and also an integer."

В примере мы объявляем новую переменную типа String, названную description и присваиваем ей исходное значение. Потом мы определяем величину integerToDescribe, используя инструкцию switch. Если значение integerToDescribe одно из значений списка кейса, то мы получаем текстовое описание значения, которое дополняется значением, которое находится в default, так как на уровень выше в сработавшем кейсе стоит ключевое слово fallthrough, после чего завершается работа инструкции switch.

Если значение integerToDescribe не принадлежит списку значений нашего единственного кейса, то срабатывает кейс по умолчанию, который имеет все оставшиеся значения, не вошедшие в первый кейс, и integerToDescribe получает значение только то, что есть в default.

После того как сработала инструкция switch, мы получаем описание числа, используя функцию print(_:separator:terminator:). В нашем примере мы получаем 5, что корректно определено как простое число.

Заметка

Ключевое слово fallthrough не проверяет условие кейса, оно позволяет провалиться из конкретного кейса в следующий или в default, что совпадает со стандартным поведением инструкции switch в языке C.

Маркированные инструкции

Вы можете размещать циклы или инструкции switch внутри других циклов или switch инструкций, создавая тем самым сложное течение исполнения кода. Однако циклы и инструкции switch могут иметь break, что может прервать выполнение кода преждевременно. В общем иногда полезно явно указывать какой цикл или какую инструкцию switch вы хотите прервать оператором break. Так же, если у вас есть несколько вложенных циклов, то может быть полезным явное указание того, на какой цикл именно будет действовать оператор continue.

Для всех этих целей мы можем маркировать цикл или инструкцию switch маркером инструкций и использовать его вместе с оператором break или оператором continue для предотвращения или продолжения исполнения маркированной инструкции.

Маркированные инструкции обозначаются меткой в той же строке, что и ключевое слово начала инструкции, которое следует после метки через двоеточие. Ниже приведен пример синтаксиса цикла while, хотя принцип работы маркера такой же со всеми инструкциями:



имя маркера: while условие {
     исполняемый код
}

В дальнейшем примере мы будем использовать break, continue с маркированным циклом while для адаптированной версии Змеи и Лестницы, которую вы видели ранее в "Циклы 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 square != finalSquare показывает, что теперь нам нужно попасть строго на клетку 25:


gameLoop: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    switch square + diceRoll {
    case finalSquare:
        //после броска кубика мы попадаем на клетку 25, игра окончена
        break gameLoop
    case let newSquare where newSquare > finalSquare:
         //мы кинули кубик на слишком большое значение, значит нам нужно кинуть снова
        continue gameLoop
    default:
        //допустимое движение по игровому полю, двигаемся
        square += diceRoll
        square += board[square]
    }
}
print("Game over!")

Игральная кость бросается в начале каждого цикла. Прежде чем двигаться по доске идет проверка в инструкции switch на валидность хода, потом обрабатывает его, если такое движение допустимо:

  • Если игральная кость двигает игрока на последнюю клетку, игра заканчивается. Оператор break с маркером gameLoop перекидывает исполнение кода на первую строку кода после цикла while, которая и завершает игру.
  • Если игральная кость двигает игрока далее чем на последнюю клетку, то такое движение считается некорректным, и игроку приходится кидать кость еще раз. Оператор continue с маркером gameLoop заканчивает итерацию и начинает новую.
  • Во всех случаях движение игрока на diceRoll клеток допустимо и каждый ход идет проверка логики игры на наличие лестниц и змей. Когда кончается итерация, мы возвращаемся на начало цикла while, где проверяется условие на необходимость дальнейших ходов.

Заметка

Если оператор break не использует маркер gameLoop, то он будет прерывать выполнение инструкции switch, а не всего цикла while. Но используя маркер gameLoop мы можем указать какое исполнение инструкции нужно прервать.

Обратите внимание так же и на то, что нет необходимости использовать маркер gameLoop, когда мы обращаемся к continue gameLoop для того, чтобы перейти к следующей итерации. В этой игре всего один цикл, так что нет никакой двусмысленности на какой цикл этот оператор может вообще воздействовать. Однако и никакого вреда нет в том, что мы явно указали gameLoop маркер в строке с оператором continue. Более того, делая так мы делаем наш код нагляднее и восприятие логики игры становится более ясным.

Ранний выход

Инструкция guard, как и инструкция if, выполняет выражения в зависимости от логического значения условия. Используйте guard, чтобы указать на то, что условие обязательно должно быть true, чтобы код после самой инструкции guard выполнился. В отличии от инструкции if, guard всегда имеет код внутри else, который выполняется, когда условие оценивается как false.


func greet(person: [String: String]) {
  guard let name = person["name"] else {
    return
  }
  
  print("Привет \(name)!")
  
  guard let location = person["location"] else {
    print("Надеюсь у тебя там хорошая погода.")
    return
  }
  
  print("Надеюсь в \(location) хорошая погода.")
}

greet(person: ["name": "John"])
// Выведет "Привет John!"
// Выведет "Надеюсь у тебя там хорошая погода."
greet(person: ["name": "Jane", "location": "Cupertino"])
// Выведет "Привет Jane!"
// Выведет "Надеюсь в Cupertino хорошая погода.”

Если условие инструкции guard выполнилось, то выполнение кода продолжается после закрывающей скобки guard. Все переменные и константы, которым мы присвоили значения с использованием опциональной привязки в качестве части условия guard, доступны нам до конца области, где был определен guard.

Если условие не выполняется, то исполняется код внутри else. Эта ветка должна перебросить исполнение кода на выход из этого блока кода, в котором был определен guard. А сделать это можно при помощи инструкций return, break, continue, throw или можно вызвать метод, который ничего не возвращает, например fatalError(_file:line:).

Использование инструкции guard для каких-либо требований улучшает читабельность кода по сравнению с if. Он помогает вам написать код, который вам не нужно будет помещать в блок else и позволит вам держать код, который обрабатывает нарушение требований рядом с самими требованиями.

Проверка доступности API

В Swift есть встроенная поддержка для проверки доступности API, благодаря которой вы будете уверены, что не используете  API-интерфейсы, недоступные для данной deployment target.

Компилятор использует информацию о доступности в SDK, чтобы убедиться, что все API-интерфейсы, используемые в коде, доступны для deployment target, указанного в вашем проекте. Swift выдает сообщение об ошибке во время компиляции, если вы пытаетесь использовать недоступный API.

Вы можете использовать условие доступности в if или guard инструкциях для того, чтобы условно выполнить блок кода, в зависимости от того, доступны ли API-интерфейсы, которые вы хотите использовать, во время выполнения. Компилятор использует информацию из условия доступности, когда проверяет доступность API-интерфейсов в этом блоке кода.


if #available(iOS 10, macOS 10.12, *) {
    // Используйте API iOS 10 для iOS и используйте API macOS 10.12 на macOS
} else {
    // Используйте более старые API для iOS и macOS
}

Условие доступности выше указывает, что на iOS тело if выполняется только на iOS 10 и более поздних версиях; что касается macOS: только на macOS 10.12 и более поздних версиях. Последний аргумент, *, требует и указывает, что на любой другой платформе, тело if выполняется на минимальной указанной deployment target.

В общем виде условие доступности принимает список названий платформ и версий. Вы можете использовать названия платформы, такие как iOSmacOSwatchOS, и tvOS; полный список можно найти в Атрибуты объявлений. В дополнение к определению основных номеров версий, такие как iOS 8 или macOS 10.10, вы можете указать второстепенные версии номера, такие как iOS 8.3 и macOS 10.10.3.

  1. if #available (название платформы версия платформы, ..., * ) {
  2. выражения для исполнения, если соответствующие условию API доступны
  3. } else {
  4. выражения для исполнения, если соответствующие условию API недоступны
  5. }
Содержание