Книга: Swift. Основы разработки приложений под iOS, iPadOS и macOS. 5-е изд. дополненное и переработанное
Назад: Часть IV. Основные возможности Swift
Дальше: 14. Опциональные типы данных

13. Операторы управления

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

В программировании для этого используются операторы управления ходом выполнения программы: представьте — вы можете останавливать работу, многократно выполнять отдельные блоки кода или игнорировать их в зависимости от возникающих условий.

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

Операторы управления можно разделить на две группы:

1. Операторы ветвления, определяющие порядок и необходимость выполнения блоков кода.

2. Операторы повторения, позволяющие многократно выполнять блоки кода.

В качестве входных данных операторам передается выражение, вычисляя значение которого они принимают решение о порядке выполнения блоков кода (игнорировать, однократно или многократно выполнять).

Выделяют следующие конструкции языка, позволяющие управлять ходом выполнения программы:

• Утверждение (глобальная функция assert(_:_:)).

• Оператор условия if.

• Оператор ветвления switch.

• Операторы повторения while и repeat while.

• Оператор повторения for.

• Оператор раннего выхода guard.

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

13.1. Утверждения

Swift позволяет прервать выполнение программы в случае, когда некоторое условие не выполняется: к примеру, если значение переменной отличается от требуемого. Для этого предназначен специальный механизм утверждений (assertions).

Утверждения в Swift реализованы в виде глобальной функции assert(_:_:).

Синтаксис

assert(проверяемое_выражение, отладочное_сообщение)

• проверяемое_выражение –> Bool — вычисляемое выражение, на основании значения которого принимается решение об экстренной остановке программы.

• отладочное_сообщение -> String — выражение, текстовое значение которого будет выведено на отладочную консоль при остановке программы. Необязательный параметр.

Функция «утверждает», что переданное ей выражение возвращает логическое значение true. В этом случае выполнение программы продолжается, в ином (если возвращен false) — выполнение программы завершается, и в отладочную консоль выводится сообщение.

Пример

// утверждение с двумя аргументами

assert( someVar > 100, "Данные неверны" )

// утверждение с одним аргументом

assert( anotherVar <= 10 )

ПРИМЕЧАНИЕ В синтаксисе выше впервые применена стрелка (->) для указания на тип данных условного элемента синтаксиса. Как говорилось ранее, она используется в том случае, когда можно передать не конкретное значение определенного типа, а целое выражение.

В листинге 13.1 показан пример использования утверждений.

Листинг 13.1

let strName = "Дракон"

let strYoung = "молод"

let strOld = "стар"

let strEmpty = " "

var dragonAge = 230

assert( dragonAge <= 235, strName+strEmpty+strOld )

assert( dragonAge >= 225, strName+strEmpty+strYoung )

print("Программа упешно завершила свою работу")

Консоль

Программа упешно завершила свою работу

В приведенном примере проверяется возраст дракона, записанный в переменную dragonAge. С помощью двух утверждений программа успешно завершит работу, только если возраст находится в интервале от 225 до 235 лет.

Первое утверждение проверяет, является ли возраст менее 235, и так как выражение dragonAge <= 235 возвращает true, программа продолжает свою работу. То же происходит и во втором утверждении, только проверяется значение выражения dragonAge >= 235.

Обратите внимание, что в качестве второго входного аргумента функции assert(_:_:) передана не текстовая строка, а выражение, которое возвращает значение типа String (происходит конкатенация строк).

Изменим значение переменной dragonAge на 220 и посмотрим на результат (листинг 13.2).

Листинг 13.2

var dragonAge = 220

assert( dragonAge <= 235, strName+strEmpty+strOld )

assert( dragonAge >= 225, strName+strEmpty+strYoung )

print("Программа упешно завершила свою работу")

Консоль

Assertion failed: Дракон молод

Первое утверждение все так же возвращает true при проверке выражения, но второе утверждение экстренно завершает работу программы, так как значение dragonAge меньше 225. В результате на консоль выведено сообщение, переданное в данную функцию.

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

Утверждения — это простейший механизм управления ходом работы программы. В частности, с его помощью невозможно выполнять различные блоки кода в зависимости от результата переданного выражения, возможно лишь аварийно завершить программу. Для решения этой проблемы в Swift имеются другие механизмы.

ПРИМЕЧАНИЕ Старайтесь не использовать утверждения в готовых к выпуску проектах. В основном они предназначены для отладки разрабатываемых программ или тестирования кода в Playground.

13.2. Оператор условия if

Утверждения, с которыми вы только что познакомились, являются упрощенной формой оператора условия. Они анализируют переданное выражение и позволяют либо продолжить выполнение программы, либо завершить его (возможно, с выводом отладочного сообщения).

Оператор if позволяет определить логику вызова блоков кода (исполнять или не исполнять) в зависимости от значения переданного выражения. Данный оператор используется повсеместно, благодаря ему можно совершать именно те действия, которые необходимы для получения корректного результата. К примеру, если пользователь нажал кнопку «Умножить», то необходимо именно перемножить, а не сложить числа.

Оператор условия if имеет четыре формы записи, различающихся по синтаксису и функциональным возможностям:

• сокращенная;

• стандартная;

• расширенная;

• тернарная.

Сокращенный синтаксис оператора if

Синтаксис

if проверяемое_выражение {

    // тело оператора

}

проверяемое_выражение -> Bool — вычисляемое выражение, на основании значения которого принимается решение об исполнении кода, находящегося в теле оператора.

Оператор условия начинается с ключевого слова if, за которым следует проверяемое выражение. Если оно возвращает true, то выполняется код из тела оператора. В противном случае код в теле игнорируется и выполнение программы продолжается кодом, следующим за оператором условия.

Как и в утверждениях, проверяемое выражение должно возвращать значение типа Bool.

Пример

if userName == "Alex" {

    print("Привет, администратор")

}

В зависимости от значения параметра userName, проверяемое выражение вернет true или false, после чего будет принято решение о вызове функции print(_:), находящейся в теле оператора.

В листинге 13.3 приведен пример использования оператора условия. В нем проверяется значение переменной logicVar.

Листинг 13.3

// переменная типа Bool

var logicVar = true

// проверка значения переменной

if logicVar == true {

   print("Переменная logicVar истинна")

}

Консоль

Переменная logicVar истинна

В качестве условия для оператора if используется выражение сравнения переменной logicVar с логическим значением true. Так как результатом этого выражения является «истина» (true), код, находящийся в теле оператора, будет исполнен, о чем свидетельствует сообщение на консоли.

Левая часть выражения logicVar == true сама по себе возвращает значение типа Bool, после чего сравнивается с true. По этой причине данное выражение является избыточным, а значит, его правая часть может быть опущена (листинг 13.3а).

Листинг 13.3а

if logicVar {

   print("Переменная logicVar истинна")

}

Консоль

Переменная logicVar истинна

Если изменить значение logicVar на false, то проверка не пройдет и функция print(_:) не будет вызвана (листинг 13.4).

Листинг 13.4

logicVar = false

if logicVar {

   print("Переменная logicVar истинна")

}

// вывод на консоли пуст

Swift — это язык со строгой типизацией. Любое проверяемое выражение обязательно должно возвращать либо true, либо false, и никак иначе. По этой причине, если оно возвращает значение другого типа, Xcode сообщит об ошибке (листинг 13.5).

Листинг 13.5

var intVar = 1

if intVar { // ОШИБКА

}

Если требуется проверить выражение не на истинность, а на ложность, то для этого достаточно сравнить его с false или добавить знак логического отрицания перед выражением (листинг 13.6).

Листинг 13.6

logicVar = false

// полная форма проверки на отрицание

if logicVar == false {

    print("Переменная logicVar ложна")

}

// сокращенная форма проверки на отрицание

if !logicVar {

    print("Переменная logicVar вновь ложна")

}

Консоль

Переменная logicVar ложна

Переменная logicVar вновь ложна

Обе формы записи из предыдущего листинга являются синонимами. В обоих случаях результатом выражения будет значение true (именно по этой причине код в теле оператора выполняется).

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

1. logicVar == false.

2. false == false — заменили переменную ее значением.

3. true — два отрицательных значения идентичны друг другу.

Во втором варианте он отличается:

1. !logicVar.

2. !false — заменили переменную ее значением.

3. TrueНЕ ложь является истиной.

ПРИМЕЧАНИЕ Если данный материал вам непонятен или кажется ошибочным, то предлагаю вновь познакомиться с описанием логического типа Bool и внимательно выполнить домашнее задание.

Стандартный синтаксис оператора if

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

Синтаксис

if проверяемое_выражение {

    // первый блок исполняемого кода (при истинности проверяемого

    // условия)

} else {

    // второй блок исполняемого кода (при ложности проверяемого

    // условия)

}

• проверяемое_выражение -> Bool — вычисляемое выражение, на основании значения которого принимается решение об исполнении кода, находящегося в соответствующем блоке.

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

Пример

if userName == "Alex" {

    print("Привет, администратор")

}else{

    print("Привет, пользователь")

}

В зависимости от значения в переменной userName будет выполнен первый или второй блок кода.

ПРИМЕЧАНИЕ В тексте книги вы будете встречаться с различными обозначениями данного оператора: оператор if, конструкция if-else и т.д. В большинстве случаев это синонимы, указывающие на использование данного оператора без привязки к какому-либо синтаксису (сокращенному или стандартному).

Рассмотрим пример использования стандартного синтаксиса конструкции if-else (листинг 13.7).

Листинг 13.7

// переменная типа Bool

var logicVar = false

// проверка значения переменной

if logicVar {

    print("Переменная logicVar истинна")

} else {

    print("Переменная logicVar ложна")

}

Консоль

Переменная logicVar ложна

В приведенном примере предусмотрен блок кода для любого варианта значения переменной logicVar.

С помощью оператора условия можно осуществить любую проверку, которая вернет логическое значение. В качестве примера определим, в какой диапазон попадает сумма двух чисел (листинг 13.8).

Листинг 13.8

var a = 1054

var b = 952

if a+b > 1000 {

    print( "Сумма больше 1000" )

}else{

    print( "Сумма меньше или равна 1000" )

}

Консоль

Сумма больше 1000

Левая часть проверяемого выражения a+b>1000 возвращает значение типа Int, после чего оно сравнивается с помощью оператора > с правой частью (которая также имеет тип Int), в результате чего получается логическое значение.

Ранее, при рассмотрении типа данных Bool, мы познакомились с операторами И (&&) и ИЛИ (||), позволяющими усложнять вычисляемое логическое выражение. Они также могут быть применены и при ­использовании конструкции if-else для проверки нескольких условий. В листинге 13.9 проверяется истинность значений двух переменных.

Листинг 13.9

// переменные типа Bool

var firstLogicVar = true

var secondLogicVar = false

// проверка значения переменных

if firstLogicVar || secondLogicVar {

    print("Одна или две переменные истинны")

} else {

    print("Обе переменные ложны")

}

Консоль

Одна из переменных истинна

Так как проверяемое выражение возвращает true, в случае, если хотя бы один из операндов равен true, в данном примере будет выполнен первый блок кода.

В приведенном выше примере есть один недостаток: невозможно отличить, когда одна, а когда две переменные имеют значение true. Для решения этой проблемы можно вкладывать операторы условия друг в друга (листинг 13.10).

Листинг 13.10

if firstLogicVar || secondLogicVar {

    if firstLogicVar && secondLogicVar {

        print("Обе переменные истинны")

    }else{

        print("Только одна из переменных истинна")

    }

} else {

    print("Обе переменные ложны")

}

Внутри тела первого оператора условия используется дополнительная конструкция if-else.

Стоит отметить, что наиболее однозначные результаты лучше проверять в первую очередь. По этой причине выражение firstLogicVar && secondLogicVar стоит вынести в первый оператор, так как оно вернет true, только если обе переменные имеют значение true. При этом вложенный оператор с выражением firstLogicVar || secondLogicVar потребуется перенести в блок else (листинг 13.10а).

Листинг 13.10а

if firstLogicVar && secondLogicVar {

    print("Обе переменные истинны")

} else {

    if firstLogicVar || secondLogicVar {

        print("Только одна из переменных истинна ")

    }else{

        print("Обе переменные ложны")

    }

}

Конструкции if-else могут вкладываться друг в друга без каких-либо ограничений, но помните, что такие «башенные» конструкции могут плохо сказываться на читабельности вашего кода.

Расширенный синтаксис оператора if

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

Синтаксис

if проверяемое_выражение_1 {

   // первый блок кода

} else if проверяемое_выражение_2 {

   // второй блок кода

}...  

} else {

    // последний блок кода

}

• проверяемое_выражение -> Bool — вычисляемое выражение, на основании значения которого принимается решение об исполнении кода, находящегося в блоке.

Если первое проверяемое условие возвращает true, то исполняется первый блок кода.

В ином случае проверяется второе выражение. Если оно возвращает true, то исполняется второй блок кода. И так далее.

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

Количество блоков else if в рамках одного оператора условия может быть произвольным, а наличие оператора else не является обязательным.

После нахождения первого выражения, которое вернет true, дальнейшие проверки не проводятся.

Пример

if userName == "Alex" {

    print("Привет, администратор")

}else if userName == "Bazil"{

    print("Привет, модератор")

}else if userName == "Helga"{

    print("Привет, редактор")

}else{

    print("Привет, пользователь")

}

Изменим код проверки значения переменных firstLogicVar и se­condLogicVar с учетом возможностей расширенного синтаксиса оператора if (листинг 13.11).

Листинг 13.11

// проверка значения переменных

if firstLogicVar && secondLogicVar {

    print("Обе переменные истинны")

} else if firstLogicVar || secondLogicVar {

    print("Одна из переменных истинна")

} else {

    print("Обе переменные ложны")

}

Консоль

Обе переменные истинны

Запомните, что, найдя первое совпадение, оператор выполняет соответствующий блок кода и прекращает свою работу. Проверки последующих условий не проводятся.

При использовании данного оператора будьте внимательны. Помните, ранее говорилось о том, что наиболее однозначные выражения необходимо располагать первыми? Если отойти от этого правила и установить значения переменных firstLogicVar и secondLogicVar в true, но выражение из else if блока (с логическим ИЛИ) расположить первым, то вывод на консоль окажется ошибочным (листинг 13.12).

Листинг 13.12

firstLogicVar = true

secondLogicVar = true

if firstLogicVar || secondLogicVar {

    print("Одна из переменных истинна")

} else if firstLogicVar && secondLogicVar {

    print("Обе переменные истинны")

} else {

    print("Обе переменные ложны")

}

Консоль

Одна из переменных истинна

Выведенное на консоль сообщение («Одна из переменных истинна») не является достоверным, так как на самом деле обе переменные имеют значение true.

Рассмотрим еще один пример использования конструкции if-else .

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

• Если жильцов менее 5 — стоимость аренды жилья равна 1000 руб­лей с человека в день.

• Если жильцов от 5 до 7 — стоимость аренды равна 800 рублям с человека в день.

• Если жильцов более 7 — стоимость аренды равна 500 рублям с человека в день.

Тарифы, конечно, довольно странные, но, тем не менее, перед вами стоит задача подсчитать общий дневной доход.

Для реализации этого алгоритма используем оператор if-else. Программа получает количество жильцов в доме и в зависимости от стоимости аренды для одного жильца возвращает общую сумму средств, которая будет получена (листинг 13.13).

Листинг 13.13

// количество жильцов в доме

var tenantCount = 6

// стоимость аренды на человека

var rentPrice = 0

/* определение цены на одного

человека в соответствии с условием */

if tenantCount < 5 {

    rentPrice = 1000

} else if tenantCount >= 5 && tenantCount <= 7 {

   rentPrice = 800

} else {

   rentPrice = 500

}

// вычисление общей суммы средств

var allPrice = rentPrice * tenantCount // 4800

Так как общее количество жильцов попадает во второй блок конструкции if-else, переменная rentPrice (сумма аренды для одного человека) принимает значение 800. Итоговая сумма равна 4800.

Кстати, данный алгоритм может быть реализован с использованием операторов диапазона и метода contains(_:), позволяющего определить, попадает ли значение в требуемый диапазон (листинг 13.13a).

Листинг 13.13а

if (..<5).contains(tenantCount) {

    rentPrice = 1000

} else if (5...7).contains(tenantCount) {

    rentPrice = 800

} else if (8...).contains(tenantCount) {

    rentPrice = 500

}

Последний else if блок можно заменить на else без указания проверяемого выражения, но показанный пример нагляднее.

Тернарный оператор условия

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

Синтаксис

проверяемое_выражение ? выражение_1 : выражение_2

• проверяемое_выражение -> Bool — вычисляемое выражение, на основании значения которого принимается решение об исполнении кода, находящегося в блоке.

• выражение_1 -> Any — выражение, значение которого будет возвращено, если проверяемое выражение вернет true.

• выражение_2 -> Any — выражение, значение которого будет возвращено, если проверяемое выражение вернет false.

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

Пример

var y = ( x > 100 ? 100 : 50 )

В данном примере в переменной y будет инициализировано значение одного из выражений (100 или 50).

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

Листинг 13.14

let a = 1

let b = 2

// сравнение значений констант

a <= b ? print("A меньше или равно B"):print("A больше B")

Консоль

A меньше или равно В

Так как значение константы a меньше значения b, то проверяемое выражение a <= b вернет true, а значит, будет выполнено первое выражение. Обратите внимание, что в данном примере тернарный оператор ничего не возвращает, а просто выполняет соответствующее выражение (так как возвращать, собственно, и нечего). Но вы можете использовать его и иначе. В листинге 13.15 показан пример того, что возвращенное оператором значение может быть использовано в составе другого выражения.

Листинг 13.15

// переменная типа Int

var height = 180

// переменная типа Bool

var isHeader = true

// вычисление значения константы

let rowHeight = height + (isHeader ? 20 : 10 )

// вывод значения переменной

rowHeight // 200

В данном примере тернарный оператор условия возвращает одно из двух целочисленных значений типа Int (20 или 10) в зависимости от логического значения переменной isHeader.

13.3. Оператор ветвления switch

Нередки случаи, когда приходится работать с большим количеством вариантов значений вычисляемого выражения, и для каждого из возможных значений необходимо выполнить определенный код. Для этого можно использовать расширенный синтаксис оператора if, многократно повторяя блоки else if.

Предположим, что в зависимости от полученной пользователем оценки стоит задача вывести определенный текст. Реализуем логику с использованием оператора if (листинг 13.16).

Листинг 13.16

// оценка

var userMark = 4

if userMark == 1 {

   print("Единица на экзамене! Это ужасно!")

} else if userMark == 2 {

   print("С двойкой ты останешься на второй год!")

} else if userMark == 3 {

   print("Ты плохо учил материал в этом году!")

} else if userMark == 4 {

   print("Неплохо, но могло быть и лучше")

} else if userMark == 5 {

   print("Бесплатное место в университете тебе обеспечено!")

} else {

   print("Переданы некорректные данные об оценке")

}

Консоль

Неплохо, но могло бы быть и лучше

Для вывода сообщения, соответствующего оценке, оператор if последовательно проходит по каждому из указанных условий, пока не найдет то, которое вернет true. Хотя программа работает корректно, но с ростом количества возможных вариантов (предположим, у вас десятибалльная шкала оценок) ориентироваться в коде будет все сложнее.

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

Синтаксис

switch проверяемое_выражение {

   case значение_1:

      // первый блок кода

   case значение_2, значение_3:

      // второй блок кода

   ...

   case значение_N:

      // N-й блок кода

   default:

      // блок кода по умолчанию

}

• проверяемое_выражение -> Any — вычисляемое выражение, значение которого Swift будет искать среди case-блоков. Может возвращать значение любого типа.

• значение:Any — значение, которое может вернуть проверяемое выражение. Должно иметь тот же тип данных.

После ключевого слова switch указывается выражение, значение которого вычисляется. После получения значения выражения производится поиск совпадающего значения среди указанных в case-блоках (после ключевых слов case).

Если совпадение найдено, то выполняется код тела соответствующего case-блока.

Если совпадение не найдено, то выполняется код из default-блока, который должен всегда располагаться последним в конструкции switch-case.

Количество блоков case может быть произвольным и определяется программистом в соответствии с контекстом задачи. После каждого ключевого слова case может быть указано любое количество значений, которые отделяются друг от друга запятыми.

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

Код, выполненный в любом блоке case, приводит к завершению работы оператора switch.

Пример

switch userMark {

    case 1,2:

        print("Экзамен не сдан")

    case 3:

    print("Необходимо выполнить дополнительное задание")

    case 4,5:

        print("Экзамен сдан")

    default:

        print("Указана некорректная оценка")

}

В зависимости от значения в переменной userMark, будет выполнен соответствующий блок кода. Если ни одно из значений, указанных после case, не подошло, то выполняется код в default-блоке.

ПРИМЕЧАНИЕ Далее в тексте книги вы будете встречать различные наименования данного оператора: оператор switch, конструкция switch-case и другие термины. Все они являются синонимами.

Перепишем программу проверки оценки, полученную учеником, с использованием конструкции switch-case. При этом вынесем вызов функции print(_:) за пределы оператора. Таким образом, если вам в будущем потребуется изменить принцип вывода сообщения (например, вы станете использовать другую функцию), то достаточно будет внести правку в одном месте кода вместо шести (листинг 13.17).

Листинг 13.17

var userMark = 4

// переменная для хранения сообщения

var message = ""

switch userMark {

case 1:

    message = "Единица на экзамене! Это ужасно!"

case 2:

    message = "С двойкой ты останешься на второй год!"

case 3:

    message = "Ты плохо учил материал в этом году!"

case 4:

    message = "Неплохо, но могло быть и лучше"

case 5:

    message = "Бесплатное место в университете тебе обеспечено!"

default:

    message = "Переданы некорректные данные об оценке"

}

// вывод сообщения на консоль

print(message)

Консоль

Неплохо, но могло быть и лучше

Оператор switch вычисляет значение переданного в него выражения (состоящего всего лишь из одной переменной userMark) и последовательно проверяет каждый блок case в поисках совпадения.

Если userMark будет иметь значение менее 1 или более 5, то будет выполнен код в блоке default.

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

ПРИМЕЧАНИЕ Обратите внимание, что переменная message объявляется вне конструкции switch-case. Это связано с тем, что если бы это делалось в каждом case-блоке, то ее область видимости была бы ограничена оператором ветвления и выражение print(message) вызвало бы ошибку.

Диапазоны в операторе switch

Одной из интересных возможностей конструкции switch-case является возможность работы с диапазонами. В листинге 13.18 приведен пример определения множества, в которое попадает заданное число. При этом используются операторы диапазона без использования метода contains(_:), как было у оператора if.

Листинг 13.18

userMark = 4

switch userMark {

case 1..<3:

    print("Экзамен не сдан")

case 3:

    print("Требуется решение дополнительного задания")

case 4...5:

    print("Экзамен сдан")

default:

    assert( false, "Указана некорректная оценка")

}

Консоль

Экзамен сдан!

Строка "Экзамен не сдан" выводится на консоль при условии, что проверяемое значение в переменной userMark попадает в диапазон 1..<3, то есть равняется 1 или 2. При значении userMark равном 3 будет выведено «Требуется решение дополнительного задания». При userMark равном 4 или 5 выводится строка « Экзамен сдан!».

Если значение userMark не попадает в диапазон 1...5, то управление переходит default-блоку, в котором используется утверждение, прерывающее программу. С помощью передачи логического false функция assert(_:_:) прекратит работу приложения в любом случае, если она была вызвана.

Другим вариантом реализации данного алгоритма может быть использование уже знакомой конструкции if-else с диапазонами и методом  contains(_:).

Кортежи в операторе switch

Конструкция switch-case оказывается очень удобной при работе с кортежами.

Рассмотрим следующий пример. Происходит взаимодействие с удаленным сервером. После каждого запроса вы получаете ответ, состоящий из цифрового кода и текстового сообщения, после чего записываете эти данные в кортеж (листинг 13.19).

Листинг 13.19

var answer: (code: Int, message: String) = (code: 404, message: "Page not found")

По коду сообщения можно определить результат взаимодействия с сервером.

Если код находится в интервале от 100 до 399, то сообщение необходимо вывести на консоль.

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

Во всех остальных сообщениях необходимо отобразить сообщение о некорректном ответе сервера.

Реализуем данный алгоритм с использованием оператора switch, передав кортеж в качестве входного выражения (листинг 13.20).

Листинг 13.20

switch answer {

case (100..<400, _):

    print( answer.message )

case (400..<500, _):

    assert( false, answer.message )

default:

    print( "Получен некорректный ответ" )

}

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

Рассмотрим другой пример.

Вы — владелец трех вольеров для драконов. Каждый вольер предназначен для содержания драконов с определенными характеристиками:

• Вольер 1 для зеленых драконов с массой менее двух тонн.

• Вольер 2 для красных драконов с массой менее двух тонн.

• Вольер 3 для зеленых и красных драконов с массой более двух тонн.

При поступлении нового дракона нужно определить, в какой вольер его поместить.

В условии задачи используются две характеристики драконов, поэтому имеет смысл обратиться к кортежам для их объединения в единое значение (листинг 13.21).

Листинг 13.21

var dragonCharacteristics: (color: String, weight: Float) = ("красный", 1.4)

switch dragonCharacteristics {

    case ("зеленый", 0..<2 ):

        print("Вольер № 1")

    case ("красный", 0..<2 ):

        print("Вольер № 2")

    case ("зеленый", 2...), ("красный", 2...):

        print("Вольер № 3")

    default:

        print("Дракон не может быть принят в стаю")

}

Консоль

Вольер № 2

Задача выполнена, при этом код получился довольно простым и ­изящным.

Ключевое слово where в операторе switch

Представьте, что в задаче про вольеры для драконов появилось дополнительное условие: поместить дракона в вольер № 3 можно только при условии, что в нем находятся менее пяти особей. Для хранения количества драконов будет использоваться дополнительная переменная.

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

С одной стороны, данный функционал можно реализовать с помощью конструкции if-else внутри последнего case-блока, но наиболее правильным вариантом станет использование ключевого слова where, позволяющего указать дополнительные требования, в том числе к значениям внешних параметров (листинг 13.22).

Листинг 13.22

var dragonsCount = 3

switch dragonCharacteristics {

case ("зеленый", 0..<2 ):

    print("Вольер № 1")

case ("красный", 0..<2 ):

    print("Вольер № 2")

case ("зеленый", 2...) where dragonsCount < 5,

     ("красный", 2...) where dragonsCount < 5:

    print("Вольер № 3")

default:

    print("Дракон не может быть принят в стаю")

}

Консоль

Вольер № 3

Ключевое слово where и дополнительные условия указываются в case-блоке для каждого значения отдельно. После where следует выражение, которое должно вернуть true или false. Тело блока будет выполнено, когда одновременно совпадет значение, указанное после case, и условие после where вернет true.

Связывание значений

Представьте ситуацию, что ваше руководство окончательно сошло с ума (думаю, многим и представлять не требуется) и выставило новое условие: вольер № 3 может принять только тех драконов, чей вес без остатка делится на единицу. Конечно, глупая ситуация, да и вряд ли ваше начальство станет отсеивать столь редких рептилий, но гипотетически такое возможно, и мы должны быть к этому готовы.

Подумайте, как бы вы решили эту задачу? Есть два подхода: используя оператор условия if внутри тела case-блока или используя where с дополнительным условием. Второй подход наиболее верный, так как не имеет смысла переходить к выполнению кода, если условия не выполняются (листинг 13.23).

Листинг 13.23

switch dragonCharacteristics {

case ("зеленый", 0..<2 ):

    print("Вольер № 1")

case ("красный", 0..<2 ):

    print("Вольер № 2")

case ("зеленый", 2...) where

        dragonCharacteristics.weight.truncatingRemainder(dividingBy:

        1) == 0

        && dragonsCount < 5,

     ("красный", 2...) where

        dragonCharacteristics.weight.truncatingRemainder(dividingBy:

        1) == 0

        && dragonsCount < 5:

    print("Вольер № 3")

default:

    print("Дракон не может быть принят в стаю")

}

Выражение dragonCharacteristics.weight.truncatingRemainder(dividingBy: 1) == 0 позволяет определить, делится ли вес дракона, указанный в кортеже, без остатка на единицу.

Хотя данный подход и является полностью рабочим, он содержит два значительных недостатка:

1. Вследствие длинного имени кортежа выражение не очень удобно читать и воспринимать.

2. Данное выражение привязывается к имени кортежа вне конструкции switch-case. Таким образом, если вы решите поменять имя с dragonCharacteristics, предположим, на dragonValues, то необходимо не только соответствующим образом изменить входное выражение, но и внести правки внутри case.

Для решения данной проблемы можно использовать прием, называемый связыванием значений. При связывании в заголовке case-блока используется ключевое слово let или var для объявления локального параметра, с которым будет связано значение. Затем данный параметр можно использовать после where и в теле case-блока.

В листинге 13.24 происходит связывание значения второго элемента кортежа dragonCharacteristics.

Листинг 13.24

switch dragonCharacteristics {

case ("зеленый", 0..<2 ):

    print("Вольер № 1")

case ("красный", 0..<2 ):

    print("Вольер № 2")

case ("зеленый", let weight) where

        weight > 2

        && dragonsCount < 5,

     ("красный", let weight) where

        weight > 2

        && dragonsCount < 5:

    print("Вольер № 3")

default:

    print("Дракон не может быть принят в стаю")

}

В заголовке case-блока для второго элемента кортежа вместо указания диапазона используется связывание его значения с локальной константой weight. Данная константа называется связанным параметром, она содержит связанное значение. Обратите внимание, что проверка веса дракона (более двух тонн) перенесена в where-условие.

После связывания значения данный параметр можно использовать не только в where-условии, но и внутри тела case-блока (листинг 13.25).

Листинг 13.25

switch dragonCharacteristics {

case ("зеленый", 0..<2 ):

    print("Вольер № 1")

case ("красный", 0..<2 ):

    print("Вольер № 2")

case ("зеленый", let weight) where

        weight > 2

        && weight.truncatingRemainder(dividingBy: 1) == 0

        && dragonsCount < 5,

     ("красный", let weight) where

        weight > 2

        && weight.truncatingRemainder(dividingBy: 1) == 0

        && dragonsCount < 5:

    print("Вольер № 3. Вес дракона \(weight) тонны")

default:

    print("Дракон не может быть принят в стаю")

}

Можно сократить и упростить код, если объявить сразу два связанных параметра, по одному для каждого элемента кортежа (листинг 13.25а).

Листинг 13.25а

switch dragonCharacteristics {

case ("зеленый", 0..<2 ):

    print("Вольер № 1")

case ("красный", 0..<2 ):

    print("Вольер № 2")

case let (color, weight) where

    (color == "зеленый" || color == "красный")

    && weight.truncatingRemainder(dividingBy: 1) == 0

    && dragonsCount < 5:

        print("Вольер № 3. Вес дракона \(weight) тонны")

default:

    print("Дракон не может быть принят в стаю")

}

Оператор break в конструкции switch-case

Если требуется, чтобы case-блок не имел исполняемого кода, то необходимо указать ключевое слово break, с помощью которого работа оператора switch будет принудительно завершена.

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

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

Листинг 13.26

var someInt = 12

switch someInt {

case 1...:

    print( "Больше 0" )

case ..<0:

    print( "Меньше 0" )

default:

    break

}

Ключевое слово fallthrough

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

Рассмотрим следующий пример: представьте, что существует три уровня готовности к чрезвычайным ситуациям — А, Б и В. Каждая степень предусматривает выполнение ряда мероприятий, причем каждый последующий уровень включает в себя мероприятия предыдущих уровней. Минимальный уровень — это В, максимальный — А (включает в себя мероприятия уровней В и Б).

Реализуем программу, выводящую на консоль все мероприятия, соответствующие текущему уровню готовности к ЧС. Так как мероприятия повторяются от уровня к уровню, то можно реализовать задуманное с помощью оператора switch с использованием ключевого слова fallthrough (листинг 13.27).

Листинг 13.27

var level: Character = "Б"

// определение уровня готовности

switch level {

    case "А":

        print("Выключить все электрические приборы ")

        fallthrough

    case "Б":

        print("Закрыть входные двери и окна ")

        fallthrough

    case "В":

        print("Соблюдать спокойствие")

    default:

        break

}

Консоль

Закрыть входные двери и окна

Соблюдать спокойствие

При значении "Б" переменной level на консоль выводятся строки, соответствующие значениям "Б" и "В". Когда программа встречает ключевое слово fallthrough, она переходит к выполнению кода следующего case-блока.

13.4. Операторы повторения while и repeat while

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

ПРИМЕЧАНИЕ Конструкции, позволяющие в зависимости от условий многократно выполнять код, называются циклами.

Операторы while и repeat while позволяют выполнять блок кода до тех пор, пока проверяемое выражение возвращает true.

Оператор while

Синтаксис

while проверяемое_выражение {

   //тело оператора

}

• проверяемое_выражение -> Bool — выражение, при истинности которого выполняется код из тела оператора.

Одно выполнение кода тела оператора называется итерацией. Итерации повторяются, пока выражение возвращает true. Его значение проверяется перед каждой итерацией.

Рассмотрим пример использования оператора while. Произведем с его помощью сложение всех чисел от 1 до 10 (листинг 13.28).

Листинг 13.28

// начальное значение

var i = 1

// хранилище результата сложения

var resultSum = 0

// цикл для подсчета суммы

while i <= 10 {

    resultSum += i

    i  += 1

}

resultSum // 55

Переменная i является счетчиком в данном цикле. Основываясь на ее значении, оператором определяется необходимость выполнения кода в теле цикла. На каждой итерации значение i увеличивается на единицу, и как только оно достигает 10, то условие, проверяемое оператором, возвращает false, после чего происходит выход из цикла.

Оператор while — это цикл с предварительной проверкой условия, то есть вначале проверяется условие, а уже потом выполняется или не выполняется код тела оператора. Если условие вернет false уже при первой проверке, то код внутри оператора проигнорируется и не будет выполнен ни одного раза.

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

Оператор repeat while

В отличие от while оператор repeat while является циклом с постпроверкой условия. В таком цикле проверка значения выражения происходит в конце итерации.

Синтаксис

repeat {

     // тело оператора

} while проверяемое_выражение

• проверяемое_выражение -> Bool — выражение, при истинности которого выполняется код из тела оператора.

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

Реализуем с помощью данного оператора рассмотренную ранее задачу сложения чисел от 1 до 10 (листинг 13.29).

Листинг 13.29

// начальное значение

var y = 1

// хранилище результата сложения

var result = 0

// цикл для подсчета суммы

repeat{

    result += y

    y += 1

} while y <= 10

result // 55

Разница между операторами while и repeat while заключается в том, что код тела оператора repeat while выполняется не менее одного раза. То есть даже если условие при первой итерации вернет false, код тела цикла к этому моменту уже будет выполнен.

Использование оператора continue

Оператор continue предназначен для перехода к очередной итерации, игнорируя следующий за ним код. Если программа встречает continue, то она незамедлительно переходит к новой итерации.

Код в листинге 13.30 производит сложение всех четных чисел в интервале от 1 до 10. Для этого в каждой итерации производится проверка на четность (по значению остатка от деления на 2).

Листинг 13.30

var x = 0

var sum = 0

while x <= 10 {

    x += 1

    if x % 2 == 1 {

        continue

    }

    sum += x

}

sum // 30

Использование оператора break

Оператор break предназначен для досрочного завершения работы цикла, с ним мы уже встречались ранее. При этом весь последующий код в теле цикла игнорируется. В листинге 13.31 показано, как производится подсчет суммы всех чисел от 1 до 54. При этом если сумма достигает 450, то происходит выход из оператора и выводится соответствующее сообщение.

Листинг 13.31

var lastNum = 54

var currentNum = 1

var sumOfInts = 0

while currentNum <= lastNum {

    sumOfInts += currentNum

    if sumOfInts > 450 {

        print("Хранилище заполнено. Последнее обработанное число -

        \(currentNum)")

        break

    }

    currentNum += 1

}

Консоль

Хранилище заполнено. Последнее обработанное число — 30

13.5. Оператор повторения for

Оператор for предназначен для цикличного выполнения блока кода для каждого элемента некоторой последовательности (Sequence). Другими словами, для каждого элемента будет выполнен один и тот же блок кода. Данный оператор принимает на вход любую последовательность (включая коллекции). К примеру, с его помощью можно вывести все символы строки (String — это Collection) по одному на консоль, вызывая функцию print(_:) для каждого символа. И для реализации этой задачи потребуется всего несколько строк кода.

Синтаксис

for связанный_параметр in последовательность {

    // тело оператора

}

• связанный_параметр — локальный параметр, которому будет инициализироваться значение очередного элемента последовательности.

• последовательность:Sequence — итерируемая последовательность, элементы которой по одному будут доступны в теле оператора через связанный параметр.

Цикл for-in выполняет код, расположенный в теле оператора, для каждого элемента в переданной последовательности. При этом перебор элементов происходит последовательно и по порядку (от первого к последнему).

Перед каждой итерацией происходит связывание значения очередного элемента последовательности с параметром, объявленным после ключевого слова for. После этого в коде тела оператора это значение доступно через имя связанного параметра. Данный (связанный) параметр является локальным для конструкции for-in и недоступен за ее пределами.

После всех итераций (перебора всех элементов последовательности) цикл завершает свою работу, а связанный параметр уничтожается.

В качестве входной последовательности может быть передана любая Sequence (в том числе Collection): массив (Array), словарь (Dictionary), множество (Set), диапазон (Range) и т.д.

Пример

for oneElementOfArray in [1,3,5,7,9]{

    // тело оператора

}

В листинге 13.32 показан пример использования оператора for, в котором производится подсчет суммы всех элементов массива, состоящего из целочисленных значений.

Листинг 13.32

// массив целых чисел

let numArray: Array<Int> = [1, 2, 3, 4, 5]

// в данной переменной будет храниться

// сумма элементов массива numArray

var result: Int = 0

// цикл подсчета суммы

for number in numArray {

    result += number

}

result //15

В данном примере с помощью for-in вычисляется сумма значений всех элементов numArray. Связанный параметр number последовательно получает значение каждого элемента массива и суммирует его с переменной result. Данная переменная объявлена вне цикла (до него), поэтому она не уничтожается после завершения работы оператора for и сохраняет свое состояние.

В ходе первой итерации переменной number присваивается значение первого элемента массива, то есть 1, после чего выполняется код тела цикла. В следующей итерации переменной number присваивается значение второго элемента (2) и повторно выполняется код тела цикла. И так далее, пока не будет выполнена последняя итерация тела оператора со значением 5 в переменной number.

В качестве входной последовательности также можно передать диапазон (листинг 13.33) или строку (листинг 13.33а).

Листинг 13.33

for number in 1...5 {

    print(number)

}

Консоль

1

2

3

4

5

Листинг 13.33а

for number in "Swift" {

    print(number)

}

Консоль

S

w

i

f

t

Связанный параметр и все объявленные в теле цикла переменные и константы — локальные, то есть недоступны вне оператора for. Если существуют внешние (относительно тела оператора) одноименные переменные или константы, то их значение не будет пересекаться с локальными (листинг 13.34).

Листинг 13.34

// внешняя переменная

var myChar = "a"

// внешняя константа

let myString = "Swift"

// цикл использует связанный параметр с именем,

// уже используемым глобальной переменной

for myChar in myString {

    // локальная константа

    // вне цикла уже существует константа с таким именем

    let myString = "Char is"

    print("\(myString) \(myChar)")

}

myChar //"a"

myString //Swift

Консоль

Char is S

Char is w

Char is i

Char is f

Char is t

Вне оператора for объявлены два параметра: переменная myChar и константа myString. В теле оператора объявляются параметры с теми же именами (myChar — связанный параметр, а myString — локальная константа). Несмотря на то что внутри цикла локальным параметрам инициализируются значения, глобальные myChar и myString не изменились.

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

ПРИМЕЧАНИЕ Хочу обратить ваше внимание еще на одну замечательную возможность Xcode playground — отображение кривой изменения значения. Если щелкнуть на темно-сером прямоугольнике в области результатов, расположенном напротив тела оператора if (из предыдущего листинга), то в редакторе кода отобразится график, позволяющий оценить, как менялось значение параметра result (рис. 13.1).

Рис. 13.1. График изменения значения параметра

Порой может возникнуть задача, когда нет необходимости использовать значения элементов коллекции, — к примеру, нужно просто трижды вывести некоторое сообщение на консоль. В этом случае для экономии памяти имя связанного параметра можно заменить на нижнее подчеркивание (листинг 13.34а).

Листинг 13.34а

for _ in 1...3 {

    print("Повторяющаяся строка")

}

Консоль

Повторяющаяся строка

Повторяющаяся строка

Повторяющаяся строка

При итерации по элементам словаря (Dictionary) можно создать отдельные связанные параметры для ключей и значений элементов (листинг 13.35).

Листинг 13.35

var countriesAndBlocks = ["Россия": "СНГ", "Франция":"ЕС"]

for (countryName, orgName) in countriesAndBlocks {

    print("\(countryName) вступила в \(orgName)")

}

Консоль

Франция вступила в ЕС

Россия вступила в СНГ

ПРИМЕЧАНИЕ Как говорилось ранее, Dictionary — это неупорядоченная коллекция. Поэтому порядок следования элементов при инициализации значения отличается от того, как выводятся данные на консоль (сперва Франция, а потом Россия, хотя при инициализации было наоборот).

Если требуется получать только ключи или только значения элементов, то можно вновь воспользоваться нижним подчеркиванием (листинг 13.36).

Листинг 13.36

var countriesAndBlocks = ["Россия": "СНГ", "Франция":"ЕС"]

for (countryName, _) in countriesAndBlocks {

    print("страна — \(countryName)")

}

for (_, orgName) in countriesAndBlocks{

    print("ораганизация — \( orgName)")

}

Помимо этого, в случае если требуется получить последовательность, состоящую только из ключей или значений словаря, можно воспользоваться свойствами keys и value и передать результат их работы в оператор for (листинг 13.37).

Листинг 13.37

countriesAndBlocks = ["Россия": "ЕАЭС", "Франция":"ЕС"]

for countryName in countriesAndBlocks.keys {

    print("страна — \(countryName)")

}

for countryName in countriesAndBlocks.values {

    print("организация — \(countryName)")

}

Если при работе с массивом для каждого элемента помимо значения требуется получить и индекс, то можно воспользоваться методом enumerated(), возвращающим последовательность кортежей, где первый элемент — индекс, а второй — значение (листинг 13.38).

Листинг 13.38

print("Несколько фактов обо мне:")

var myMusicStyles = ["Rock", "Jazz", "Pop"]

for (index, musicName) in myMusicStyles.enumerated() {

    print("\(index+1). Я люблю \(musicName)")

}

Консоль

Несколько фактов обо мне:

1. Я люблю Rock

2. Я люблю Jazz

3. Я люблю Pop

Вновь вернемся к работе с последовательностями, состоящими из чисел.

Предположим, что перед вами стоит задача обработать все числа от 1 до 10, идущих с шагом 3 (массив значений 1, 4, 7, 10). В этом случае вы можете «руками» создать коллекцию с необходимыми элементами и передать ее в конструкцию for-in (листинг 13.39).

Листинг 13.39

// коллекция элементов от 1 до 10 с шагом 3

var intNumbers: Array = [1, 4, 7, 10]

for element in intNumbers{

    // код, обрабатывающий очередной элемент

}

Если диапазон чисел будет не таким маленьким, а значительно шире (например: от 1 до 1000 с шагом 5), то самостоятельно описать его будет затруднительно. Также возможна ситуация, когда характеристики множества (начальное и конечное значение, а также шаг) заранее могут быть неизвестны.  В этом случае удобнее всего воспользоваться специальными функциями stride(from:through:by:) или stride(from:to:by:), формирующими последовательность (Sequence) элементов на основе указанных правил.

Функция stride(from:through:by:) возвращает последовательность числовых элементов, начиная с from до through с шагом by (лис­тинг 13.40).

Листинг 13.40

for i in stride( from:1, through: 10, by: 3 ) {

    // тело оператора

}

Параметр i будет последовательно принимать значения 1, 4, 7, 10.

Функция stride(from:to:by:) имеет лишь одно отличие — вместо входного параметра through используется to, который исключает указанное в нем значение из последовательности (листинг 13.41).

Листинг 13.41

for i in stride( from:1, to: 10, by:3 ) {

    // тело оператора

}

Параметр i будет получать значения 1, 4 и 7.

В листинге 13.42 приведен пример вычисления суммы всех нечетных чисел от 1 до 1000 с помощью функции stride(from:through:by:).

Листинг 13.42

var result = 0

for i in stride( from:1, through: 1000, by:2 ) {

    result += i

}

result // 250000

ПРИМЕЧАНИЕ Коллекции элементов, возвращаемые функциями stride(from:through:by:) и stride(from:to:by:), представлены в виде значений специаль­ных типов данных StrideThrough и StrideTo (если точнее, то StrideTo<T> и StrideThrough<T>, где T — это тип данных элементов коллекции).

Уверен, что вы обратили внимание, что по ходу изучения материала появляется все больше новых и порой непонятных типов данных. Дело в том, что архитектура Swift создана так, что для каждой цели используется свой тип данных, и в этом нет ничего страшного. Со временем, активно создавая приложения и изучая справочную документацию, вы будете без каких-либо проблем ориентироваться во всем многообразии доступных возможностей. И уже скоро начнете создавать собственные типы данных.

Использование where в конструкции for-in

Одной из замечательных возможностей оператора for является использование ключевого слова where для указания дополнительных условий итерации элементов последовательности.

Вернемся к примеру подсчета суммы всех четных чисел от 1 до 10. Для этой цели можно использовать оператор for совместно с where-условием (листинг 13.43).

Листинг 13.43

var result = 0

for i in 1...10 where i % 2 == 0 {

    result += i

}

result // 30

После where указано условие, в котором определено, что код в теле оператора будет исполняться только в случае, когда остаток от деления связанного параметра i на 2 равен 0 (то есть число четное).

Также с помощью where можно уйти от использования вложенных друг в друга операторов (например, for в if). В листинге 13.44 показаны два блока: первый с использованием двух операторов, а второй с использованием for и where. При этом обе конструкции функционально идентичны.

Листинг 13.44

var isRun = true

// вариант 1

if isRun {

    for i in 1...10 {

        // тело оператора

    }

}

// вариант 2

for i in 1...10 where isRun {

    // тело оператора

}

В обоих вариантах код тела оператора будет выполнен 10 раз при условии, что isRun истинно, но при этом вариант № 2 более читабельный.

ПРИМЕЧАНИЕ В данном примере вариант 2 пусть и является более читабельным, но потенциально создает большую нагрузку на компьютер. Если значение isRun будет ложным (false), то вариант 1 единожды проверит его и пропустит вложенный оператор for, в то время как вариант 2 предусматривает выполнение проверки 10 раз (при каждой итерации).

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

Многомерные коллекции в конструкции for-in

Если перед вами стоит задача обработки многомерных коллекций, то вы можете с легкость организовать это, а именно вкладывать конструкции for-in друг в друга.

В листинге 13.45 объявляется словарь, содержащий результаты игр одной хоккейной команды в чемпионате. Ключ каждого элемента — это название команды соперника, а значение каждого элемента — массив результатов ее игр. На консоль выводятся результаты всех игр с указанием команд и итогового счета.

Листинг 13.45

// словарь с результатами игр

var resultsOfGames = ["Red Wings":["2:1","2:3"], "Capitals":["3:6","5:5"],"Penguins":["3:3","1:2"]]

// обработка словаря

for (teamName, results) in resultsOfGames {

    // обработка массива результатов игр

    for oneResult in results {

        print("Игра с \(teamName)  — \(oneResult)")

    }

}

Консоль:

Игра с Capitals — 3:6

Игра с Capitals — 5:5

Игра с Red Wings — 2:1

Игра с Red Wings — 2:3

Игра с Penguins — 3:3

Игра с Penguins — 1:2

Параметр resultsOfGames — «словарь массивов» с типом [String: [String]]. Переменная teamName — локальная для родительского ­оператора for, но в ее область видимости попадает вложенный for-in, поэтому она может быть использована при выводе значения на консоль.

Использование continue в конструкции for-in

Оператор continue предназначен для перехода к очередной итерации, игнорируя следующий за ним код тела оператора.

В листинге 13.46 представлена программа, в которой переменная поочередно принимает значения от 1 до 10, причем когда значение нечетное, оно выводится на консоль.

Листинг 13.46

for i in 1...10 {

    if i % 2 == 0 {

     continue

    } else {

     print(i)

    }

}

Консоль:

1

3

5

7

9

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

Использование break в конструкции for-in

Оператор break предназначен для досрочного завершения работы цикла. При этом весь последующий код в теле цикла игнорируется.

В листинге 13.47 многократно (потенциально бесконечно) случайным образом вычисляется число в пределах от 1 до 10. Если это число равно 5, то на консоль выводится сообщение с номером итерации и выполнение цикла завершается.

Листинг 13.47

import Foundation

for i in 1... {

    let randNum = Int(arc4random_uniform(100))

    if randNum == 5 {

        print("Итерация номер \(i)")

        break

    }

}

Консоль:

Итерация номер 140

Обратите внимание, что в качестве входного множества используется оператор диапазона 1.... С его помощью цикл будет выполняться до тех пор, пока не будет выполнено условие достижения break, то есть пока randNum не станет равно 5.

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

Функция arc4random_uniform() принимает на вход параметр типа Uint32 и возвращает случайное число в диапазоне от 0 до переданного значения типа UInt32. Возвращаемое случайное число также имеет тип данных UInt32, поэтому его необходимо привести к Int.

ПРИМЕЧАНИЕ Сейчас мы не будем подробно рассматривать директиву import. Пока вам необходимо запомнить лишь то, что она подгружает в программу внешнюю библиотеку, благодаря чему обеспечивается доступ к ее ресурсам. Подробнее об использовании команды import и существующих библиотеках функции будет рассказано в одной из следующих глав.

В данном примере подгружается библиотека Foundation для обеспечения доступа к функции arc4random_uniform(), которая предназначена для генерации случайного числа. Если убрать строку import Foundation, то Xcode сообщит о том, что функции arc4random_uniform() не существует.

Может возникнуть ситуация, когда из внутреннего цикла необходимо прервать выполнение внешнего, для этого в Swift используются метки (листинг 13.48).

Листинг 13.48

mainLoop: for i in 1...5 {

    for y in i...5 {

        if y == 4 && i == 2{

            break mainLoop

        }

        print("\(i) — \(y)")

    }

}

Консоль:

1 — 1

1 — 2

1 — 3

1 — 4

1 — 5

2 — 2

2 — 3

Метка представляет собой произвольный набор символов, который ставится перед оператором повторения и отделяется от него двоеточием.

Чтобы изменить ход работы внешнего цикла, после оператора break или continue необходимо указать имя метки.

13.6. Оператор досрочного выхода guard

Оператор guard называется оператором досрочного выхода. Подобно оператору if он проверяет истинность переданного ему условия. Отличие его в том, что он выполняет код в теле оператора только в том случае, если условие вернуло значение false.

Синтаксис

    guard проверяемое_условие else {

       // тело оператора

    }

После ключевого слова guard следует некоторое проверяемое утверждение. Если утверждение возвращает true, то тело оператора игнорируется и управление переходит следующему за guard коду.

Если утверждение возвращает false, то выполняется код внутри тела оператора.

Для данного оператора существует ограничение: его тело должно содержать один из следующих операторов — return, break, continue, throw.

В дальнейшем мы более подробно познакомимся с guard и рассмотрим примеры его использования.

Назад: Часть IV. Основные возможности Swift
Дальше: 14. Опциональные типы данных