Link Search Menu Expand Document

Потік керування

Мова Swift надає багато різноманітних інструкцій потоку керування. Вони охоплюють цикли while, щоб виконувати задачу кілька разів; інструкції if, guard, та switch для розгалуження коду в залежності від певних умов; інструкції на кшталт break та continue для передачі керування до іншої точки у вашому коді.

Мова Swift також надає цикл for-in, що спрощує ітерування масивів, словників, діапазонів, рядків та інших послідовностей.

Інструкція switch у Swift значно потужніша за її аналоги у багатьох C-подібних мовах. Випадки в інструкції switch не переходять автоматично до наступного випадку у Swift, що унеможливлює типові помилки мови C спричинені пропущеною інструкцією break. Випадки можуть співпадати із багатьма різними шаблонами, включаючи потрапляння до інтервалу, кортежі, чи приведення до певних типів. При співпадінні значення у випадках switch може бути прив’язане до тимчасової константи чи змінної для використання у тілі випадку, а складні умови співпадіння можуть виражатись за допомогою пункту where для кожного випадку.

Цикл For-In

Цикл for-in використовується для ітерування послідовності, такої як діапазон чисел, елементи масиву, чи символи в рядку.

У наступному прикладі друкуються перші кілька рядків таблиці множення на п’ять:

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 є константою, чиє значення автоматично встановлюється на початку кожної ітерації циклу. Константа index як така не зобов’язана бути оголошеною до використання. Вона неявно оголошена просто за допомогою входження її в оголошення циклу, без необхідності використання ключового слова let.

Якщо вам не потрібно кожне значення в послідовності, їх можна проігнорувати записавши символ підкреслення замість імені змінної.

let base = 3
let power = 10
var answer = 1
for _ in 1...power {
    answer *= base
}
print("\(base) в степені \(power) дорівнює \(answer)")
// Надрукує "3 в степені 10 дорівнює 59049"

У прикладі вище обчислюється значення числа base в степені power (в даному випадку, 3 в степені 10). Початкове значення 1 (тобто 3 в степені 0) перемножується на 3 десять разів, що вказано за допомогою закритого діапазону, від 1 до 10. Для даного обчислення, поточне значення лічильника всередині циклу не потрібне, код просто виконує цикл потрібну кількість разів. Використання символу підкреслення (_) замість змінної циклу дозволяє ігнорувати окремі значення і не надавати доступ до поточного значення всередині кожної ітерації циклу.

Цикл for-in зручно використовувати для ітерування елементів масиву.

let names = ["Карпо", "Мотря", "Лаврін", "Мелашка"]
for name in names {
    print("Привіт, \(name)!")
}
// Привіт, Карпо!
// Привіт, Мотря!
// Привіт, Лаврін!
// Привіт, Мелашка!

Також можна ітерувати елементи словнику для доступу до пар ключ-значення. Під час ітерації словнику, кожен елемент зі словника повертається як кортеж (key, value), і ви можете робити декомпозицію елементів кортежу (key, value) у явно іменовані константи для використання їх у тілі циклу for-in. Тут, ключі словника декомпонуються у константу на ім’я animalName, а значення словника декомпонуються у константу на ім’я legCount.

let numberOfLegs = ["павуки": 8, "мурахи": 6, "коти": 4]
for (animalName, legCount) in numberOfLegs {
    print("\(animalName) мають \(legCount) ніг")
}
// мурахи мають 6 ніг
// павуки мають 8 ніг
// коти мають 4 ніг

Елементи у словнику Dictionary не обов’язково ітеруватимуться в тому ж порядку, в якому їх було додано в словник. Вміст словнику по суті невпорядкований, тому їх ітерування не гарантує порядку, в якому з’являтимуться елементи словнику. Детальнішу інформацію про масиви та словники можна знайти в розділі Колекції.

Цикли While

Цикли while виконують набір інструкцій, допоки умова не стане хибною (false). Ці типи циклів найкраще використовувати тоді, коли кількість ітерацій невідома до початку першої ітерації. У Swift є два види циклів while:

  • Цикл while перевіряє умову на початку кожного проходження циклу.
  • Цикл repeat-while перевіряє умову в кінці кожного проходження циклу.

While

Цикл while починається із перевірки єдиної умови. Якщо умова виконується (true), то виконується набір інструкцій до тих пір, поки умова не перестане виконуватись (false).

Ось загальна форма циклу while:

while <умова> {
    <інструкції>
}

Наступний приклад демонструє просту гру Ліла (https://uk.wikipedia.org/wiki/Ліла_(гра (також відому як Змії та сходи):



Правила гри наступні:

  • Дошка має 25 клітин, і метою є потрапити на клітину 25 або за неї.
  • Під час кожного ходу, ви кидаєте гральні кості, і рухаєтесь на відповідне число клітин вперед, слідуючи горизонтальним шляхом відповідно до штрих-пунктирної стрілки вище.
  • Якщо хід закінчується на клітинці із нижньою частиною сходів, ви підіймаєтесь угору по цих сходам
  • Якщо хід закінчується на голові змії, ви спускаєтесь униз по цій змії

Дошку представлено масивом цілочисельних значень (Int). Її розмір визначається константою на ім’я finalSquare, котра використовується для ініціалізації масиву і для перевірки виграшу в умові далі в цьому прикладі. Дошка ініціалізується 26-ма нульовими значеннями Int, не 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] присвоєно значення 8 (різниця між 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("Гру завершено!")

У прикладі вище використовується дуже простий підхід до кидання костей. Замість генерування випадкового числа, ми починаємо із нульового значення diceRoll. Кожної ітерації циклу while змінна diceRoll збільшується на 1, та перевіряється, чи не стало це значення завеликим. Щоразу коли змінна 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], що більше за розмір цього масиву.

Поточна ітерація циклу while закінчується, і далі перевіряється умова, щоб дізнатись, що потрібна ще одна ітерація циклу. Якщо гравець прийшов на клітинку 25 або за неї, умова циклу обчислюється як false, а гра закінчується.

Цикл while є доречним у даному випадку, бо тривалість гри є невідомою на початок циклу while. Цикл триває до моменту виконання умови закінчення гри.

Цикли Repeat-While

Інша варіація циклу while, відома як цикл repeat-while, виконує одну ітерацію циклу перед тим, як перевіряє умову циклу. Після цього вона повторює ітерації циклу допоки умова не стане хибною (false).

Примітка

Цикл repeat-while у Swift є аналогом циклу do-while у інших мовах.

Ось загальна форма циклу repeat-while:

repeat {
    <інструкції>
} while <умова>

Переглянемо ще раз приклад зі грою Ліла, переписавши його за допомогою циклу repeat-while замість while. Значення змінних finalSquare, board, square, та diceRoll ініціалізовано точно так само як і з циклом while.

let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0

В цій версії гри, першою дією в циклі є перевірка на сходи чи змій. Жодні сходи у грі не ведуть гравця до клітинки 25, і тому неможливо виграти у грі, тільки рухаючись вверх по драбині. Таким чином, допустимо перевіряти на сходи чи змій першою дією в циклі.

На початку гри, гравець знаходиться у “нульовій клітинці. board[0] завжди дорівнює 0 і тому ніяк не впливає.

repeat {
    // рухаємось вверх чи вниз у випадку сходів чи змії:
    square += board[square]
    // кидаємо кості
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // рухаємось на кількість клітинок, що випала під час кидання костей:
    square += diceRoll
} while square < finalSquare
print("Гру завершено!")

Після того, як код перевіряє клітинку на сходи чи змії, кидаються кості, і гравець рухається вперед на diceRoll клітинок. Поточна ітерація циклу закінчується.

Умова циклу (while square < finalSquare) така ж як і в попередньому прикладі, але цього разу вона не перевіряється допоки не закінчиться перша ітерація циклу. Структура циклу repeat-while в прикладі вище краще підходить для даної гри, ніж структура циклу while в попередньому прикладі. В циклі repeat-while вище, square += board[square] завжди виконується одразу після того, як виконання умови циклу підтвердить, що square все ще знаходиться в межах ігрової дошки. Дана поведінка позбавляє необхідності перевіряти потрапляння в межі масиву, котра виконувалась в попередній версії цієї гри.

Інструкції умови

Дуже часто потрібно виконувати різні частини коду в залежності від певних умов. Вам може бути необхідно виконати додаткову частину коду у випадку помилки, або для виведення повідомлення, коли якесь значення стає занадто великим або малим. Для цього частини коду роблять умовними.

У мові Swift є два способи додавати умовні гілки коду: інструкція if та інструкція switch. Як правило, інструкція if використовується для перевірки простих умов із невеликою кількістю можливих результатів. Інструкція switch краще підходить для складніших умов із багатьма можливими варіантами, і є корисною в ситуаціях, коли співпадіння шаблону може допомогти обрати потрібну гілку коду для виконання.

If

У найпростішій формі, інструкція if має єдину умову. Вона виконує набір інструкцій тільки тоді, коли значення умови обчислюється як true.

var temperatureInCelsius = -1
if temperatureInCelsius <= 0 {
    print("Дуже холодно. Варто вдягнути шарф.")
}
// Надрукує "Дуже холодно. Варто вдягнути шарф."

Наступний приклад перевіряє, чи є значення температури менше або рівне 0°C (точці замерзання води). Якщо це так, буде надруковане повідомлення. В іншому випадку не буде надруковано жодного повідомлення, і виконання коду буде продовжено від закриваючої фігурної дужки.

В інструкції if можна вписати альтернативний набір інструкцій, відомий як пункт else, для ситуацій, коли умова if обчислюється як false. Ці інструкції позначаються ключовим словом else.

temperatureInCelsius = 5
if temperatureInCelsius <= 0 {
    print("Дуже холодно. Варто вдягнути шарф.")
} else {
    print("Не так вже й холодно, вдягайте футболку.")
}
// Надрукує "Не так вже й холодно, вдягайте футболку."

Одна з цих гілок коду завжди буде виконана. Оскільки температура піднялась до 5°C, вже не достатньо холодно, щоб радити вдягнути шарф, і тому виконується гілка коду else.

Можна об’єднувати кілька інструкцій if у ланцюжок для розгляду додаткових умов.

    temperatureInCelsius = 32
if temperatureInCelsius <= 0 {
    print("Дуже холодно. Варто вдягнути шарф.")
} else if temperatureInCelsius >= 30 {
    print("Спекотно. Не забудьте скористуватись сонцезахисним кремом.")
} else {
    print("Не так вже й холодно, вдягайте футболку.")
}
// Надрукує "Спекотно. Не забудьте скористуватись сонцезахисним кремом."

У прикладі вище, було додано додаткову інструкцію if, щоб реагувати на особливо високі температури. Фінальна гілка else залишається незмінною, але тепер вона друкує повідомлення для температур, що не є занадто високими чи занадто низькими.

Однак, фінальна гілка else не є обов’язковою, і може бути пропущеною, якщо набір умов не повинен бути повним.

temperatureInCelsius = 22
if temperatureInCelsius <= 32 {
    print("Дуже холодно. Варто вдягнути шарф.")
} else if temperatureInCelsius >= 86 {
    print("Спекотно. Не забудьте скористуватись сонцезахисним кремом.")
}

Оскільки температура не є ні занадто низькою, ні занадто високою, щоб спрацювала умова if чи else if, не буде надруковано жодного повідомлення.

Switch

Інструкція switch розглядає значення і порівнює його із кількома можливими шаблонами. Після цього виконується відповідний блок коду, що визначається першим шаблоном, з яким співпало значення. Інструкція switch є альтернативою інструкції if для реагування на кілька можливих станів.

У найпростішій формі, інструкція switch порівнює значення з одним чи багатьма значеннями одного типу.

switch <якесь значення для розглядуи> {
case <значення 1>:
    <реакція на значення 1>
case <значення 2>,
     <значення 3>:
    <реакція на значення 2 чи 3>
default:
    <інакше, робимо щось інше>
}

Кожна інструкція switch складається із кількох можливих випадків, кожен із яких починається ключовим словом case. Окрім простого порівняння із конкретним значенням, у Swift є кілька способів вказати складніші шаблони в кожному випаду. Ці можливості описані далі у цьому розділі.

Як і тіло в інструкції if, кожен випадок case є окремою гілкою виконання коду. Інструкція switch визначає, яку із гілок виконати. Ця процедура відома як перемикання значення, що розглядається.

Кожна інструкція switch повинна бути вичерпною. Це означає, що кожне можливе значення, що розглядається, повинно співпасти з одним випадком у switch. Якщо в якійсь ситуації недоречно надавати випадок для кожного можливого значення, можна визначити випадок за замовчанням, котрий покриє всі значення, котрі не опрацьовані явно. Випадок за замовчанням позначається ключовим словом default і повинен завжди слідувати останнім.

У наступному прикладі інструкція switch використовується для розгляду єдиного рядкового символу на ім’я someCharacter:

let someCharacter: Character = "я"
switch someCharacter {
case "а":
    print("Перша буква абетки")
case "я":
    print("Остання буква абетки")
default:
    print("Якась інша літера")
}
// Надрукує "Остання буква абетки"

Перший випадок інструкції switch порівнює значення із першою літерою української абетки, а, а другий випадок порівнює значення з останньою літерою, я. Оскільки switch повинен мати випадок для будь-якого можливого символа, не тільки для символів алфавіту, в інструкції switch використовується випадок за замовчанням (default) для всіх інших символів крім а та я. Це положення забезпечує вичерпність інструкції switch.

Відсутність неявного переходу до наступного випадку

На відміну від інструкції switch в мовах C та Objective-C, інструкція switch у Swift не переходить вкінці випадку до наступного випадку неявно. Замість цього, виконання інструкції switch закінчується після виконання першого випадку, що підійшов, без необхідності вказування інструкції break явно. Це робить інструкцію switch легшою у використанні ніж її аналоги в C та запобігає випадковому виконанню більш ніж одного випадку.

Примітка

Хоч іструкція break і не обов’язкова у Swift, її тим не менше можна використовувати у випадках інструкції switch для ігнорування певного випадку, або для раннього виходу з нього. Більш детальну інформацію можна знайти в підрозділі Інструкція Break в інструкції Switch.

Тіло кожного випадку повинно містити хоча б одну інструкцію для виконання. Наступний зразок коду не є коректним, бо перший випадок порожній:

let anotherCharacter: Character = "а"
switch anotherCharacter {
case "а": // Некоректно, бо тіло випадку порожнє
case "А":
    print("Літера A")
default:
    print("Не літера A")
}
// Тут буде помилка компіляції.

На відміну від інструкції switch у C, дана інструкція switch не співпадає одночасно із "a" та "A". Замість цього буде виведено помилка часу компіляції, про те, що case "a": не містить жодної інструкції для виконання. Цей підхід унеможливлює випадкові неявні переходи до наступного випадку, робить код безпечнішим, а його наміри зрозумілішими.

Щоб створити інструкцію switch з одним випадком, що співпадає одночасно із "a" та "A", треба поєднати ці два значення в об’єднаному випадку, розділивши їх комою.

let anotherCharacter: Character = "а"
switch anotherCharacter {
case "а", "А":
    print("Літера А")
default:
    print("Не літера А")
}
// Надрукує "Літера A"

Для легкості читання, об’єднані випадки також можна записувати на декількох рядках. За детальнішою інформацією про об’єднані випадки, звертайтесь до підрозділу [Об’єднані випадки](#Об’єднані-випадки).

Примітка

Для явного переходу до наступного випадку, слід у кінці поточного випадку вказати ключове слово fallthrough, як зазначено в підрозіді

To explicitly fall through at the end of a particular switch case, use the fallthrough keyword, as described in Перехід до наступного випадку.

Співпадіння з інтервалом

У випадках інструкції switch значення можуть перевірятись на входження в інтервал. У наступному прикладі, для опису природною мовою кількостей супутників планети використовуються числові інтервали:

let approximateCount = 62
let countedThings = "На орбіті Сатурна"
var naturalCount: String
switch approximateCount {
case 0:
    naturalCount = "жодного супутника"
case 1..<5:
    naturalCount = "декілька супутників"
case 5..<10:
    naturalCount = "численні супутники"
case 10..<100:
    naturalCount = "десятки супутників"
case 100..<1000:
    naturalCount = "сотні супутників"
default:
    naturalCount = "багато супутників"
}
print("\(countedThings) \(naturalCount).")
// Надрукує "На орбіті Сатурна десятки супутників."

У прикладі вище, в інструкції switch розглядається значення approximateCount. В кожному випадку воно порівнюється із числом або інтервалом. Оскільки значення approximateCount знаходиться між 10 та 100,
In the above example, approximateCount is evaluated in a switch statement. Each case compares that value to a number or interval. Because the value of approximateCount falls between 12 and 100, змінній naturalCount присвоєно значення "десятки супутників", і виконання виходить з інструкції switch.

Кортежі

В одній інструкції switch можна перевіряти одразу кілька значень, об’єднавши їх у кортеж. Кожен елемент кортежу може порівнюватись з окремим значенням чи інтервалом значень. Також можна використовувати символ підкреслення (_), також відомий як шаблон підстановки, для співпадіння із будь-яким можливим значенням.

У наступному прикладі розглядається точка (x, y), виражена за допомогою простого кортежу типу (Int, Int), та характеризується попадання цієї точки на один із графіків на ілюстрації.

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("(0, 0) в початку координат")
case (_, 0):
    print("(\(somePoint.0), 0) знаходиться на осі x")
case (0, _):
    print("(0, \(somePoint.1)) знаходиться на осі y")
case (-2...2, -2...2):
    print("(\(somePoint.0), \(somePoint.1)) всередині квадрату")
default:
    print("(\(somePoint.0), \(somePoint.1)) за межами квадрату")
}
// Надрукує "(1, 1) всередині квадрату"

Інструкція switch визначає, чи знаходиться точка в початку координат (0, 0), на осі x, на осі y, всередині синього квадрата розміром 4х4 з центром в початку координат, або за його межами.

На відміну від C, Swift дозволяє кільком випадкам switch розглядати однакове значення. Фактично, точка (0, 0) співпадає із чотирма випадками у прикладі вище. Однак, у разі якщо можливо кілька співпадінь, завжди виконується первий випадок. Точка (0, 0) спершу співпадає із case (0, 0), тому всі подальші співпадіння ігноруються.

Прив’язування значень

У випадках інструкції switch, одне чи кілька значень можна прив’язати до тимчасових констант чи змінних, для використання у тілі випадку. Така поведінка відома як прив’язування значень, бо значення прив’язуються до тимчасових констант чи змінних всередині тіла випадку.

У наступному прикладі розглядається точка (x, y), виражена кортежем типу (Int, Int), і категоризується її потрапляння на графік:

let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("На осі x зі значенням x = \(x)")
case (0, let y):
    print("На осі y зі значенням y = \(y)")
case let (x, y):
    print("Деінде з координатами (\(x), \(y))")
}
// Надрукує "На осі x зі значенням x = 2"

Дана інструкція switch визначає, чи потрапляє точна на червону вісь x, чи на жовтогарячу вісь y, чи деінде (на жодну з осей).

Усі три випадки switch оголошують тимчасові константи x and y, яким присвоюється одне зі значень кортежу anotherPoint, чи обидва. У першому випадку, case (let x, 0) співпадає із будь-якою точкою, в якій y дорівнює 0, а значення x присвоєно тимчасовій константі x. Аналогічно, у другому випадку case (0, let y) співпадає із будь-якою точкою, в якій x дорівнює 0, а значення y присвоєно тимчасовій константі y.

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

Ця інструкція switch не має випадку за замовчанням default. Останній випадок, case let (x, y) оголошує кортеж із двох констант, що співпадає із будь-яким значенням. Оскільки кортеж anotherPoint завжди містить два значення, цей випадок співпадає із будь-яким значенням, і тому дана інструкція switch вичерпна і не потребує випадку за замовчанням default.

Where

A switch case can use a where clause to check for additional conditions.

The example below categorizes an (x, y) point on the following graph:

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"

The switch statement determines whether the point is on the green diagonal line where x == y, on the purple diagonal line where x == -y, or neither.

The three switch cases declare placeholder constants x and y, which temporarily take on the two tuple values from yetAnotherPoint. These constants are used as part of a where clause, to create a dynamic filter. The switch case matches the current value of point only if the where clause’s condition evaluates to true for that value.

As in the previous example, the final case matches all possible remaining values, and so a default case is not needed to make the switch statement exhaustive.

Об’єднані випадки

Коли є декілька випадків інструкції switch, котрі мають однакове тіло, їх можна об’єднувати, записуючи декілька шаблонів після ключового слова case та розмежовуючи їх комами. Якщо значення, котре розглядається, співпаде хоча б з одним із шаблонів, буде виконано тіло випадку. Шаблони можна записати у кілька рядків, якщо їх список довгий. Наприклад:

let someCharacter: Character = "е"
switch someCharacter {
case "а", "е", "и", "i", "о", "у", "я", "ю", "є", "ï":
    print("\(someCharacter) є голосною")
case "б", "в", "г", "ґ", "д", "ж", "з", "й", "к", "л",
     "м", "н", "п", "р", "с", "т", "ф", "х", "ц", "ш", "щ":
    print("\(someCharacter) є приголосною")
default:
    print("\(someCharacter) не є ні голосною ні приголосною")
}
// Надрукує "е є голосною"

Перший випадок інструкції switch співпадає із десятьма голосними української мови. Аналогічно, другий випадок співпадає з усіма приголосними української мови. Випадок за замовчанням default співпадає із будь-яким іншим символом.

Об’єднані випадки також можуть містити прив’язані значення. Всі шаблони в об’єднаному випадку повинні містити однаковий набір прив’язаних значень, і кожне прив’язане значення повинно мати однаковий тип. Таким чином, незалежно від того, з яким із шаблонів співпало значення, код у тілі випадку зажди отримує значення одного й того ж типу.

let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
    print("Точка лежить на осі, на відстані \(distance) від початку координат")
default:
    print("Точка не лежить на осі")
}
// Надрукує "Точка лежить на осі, на відстані 9 від початку координат"

У випадку вище є два шаблони: (let distance, 0), що співпадає із точками на осі Х, та (0, let distance), що співпадає із точками на осі Y. Обидва шаблони містять прив’язане значення distance, котре є цілим числом в обох випадках – що означає, що код у тілі випадку завжди має доступ до значення distance.

Інструкції передачі контролю

Інструкції передачі контролю міняють порядок, у якому виконується код, передаючи контроль з однієї секції коду в іншу. У мові Swift є п’ять інструкцій передачі контролю:

  • continue
  • break
  • fallthrough
  • return
  • throw

Інструкції continue, break, та fallthrough описані нижче. Інструкція return описана у розділі Функції, а інструкція throw описана в підрозділі Поширення помилок за допомогою функцій, що викидають помилки.

Інструкція Continue

Інструкція continue вказує поточному циклу, що слід припинити виконання поточної ітерації та почати наступну ітерацію циклу. Вона каже “я закінчив із поточною ітерацією циклу” без повного виходу із циклу.

У наступному прикладі із рядка, записаного малими літерами, видаляються всі голосні, пробіли та тире, для створення фрази-загадки:

let puzzleInput = "борітеся – поборете"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["а", "е", "и", "i", "о", "у", "я", "ю", "є", "ï", " ", "–"]
for character in puzzleInput.characters {
    if charactersToRemove.contains(character) {
        continue
    } else {
        puzzleOutput.append(character)
    }
}
print(puzzleOutput)
// Надрукує "бртспбрт"

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

Інструкція Break

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

Інструкція Break у циклі

При використанні всередині циклу, інструкція break завершує виконання циклу та негайно передає контроль до коду після фігурної дужки, що закриває тіло циклу (}). Подальший код в поточній ітерації не виконується, а наступні ітерації циклу не розпочинаються.

Інструкція Break в інструкції Switch

При використанні всередині інструкції switch, інструкція break негайно завершує її виконання і передає контроль до коду після фігурної дужки, що закриває switch (}).

Ця поведінка може бути використана для ігнорування одного чи більше випадків в інструкції switch. Інструкція switch у мові Swift повинна бути вичерпною і не дозволяє створювати порожні випадки, але при цьому іноді потрібно свідомо ігнорувати певні випадки, щоб зробити намір коду явним. Зробити це можна, написавши інструкцію break замість тіла випадку, який потрібно проігнорувати. Коли виконання потрапляє у такий випадок, інструкція break всередині його негайно зупиняє виконання інструкції switch.

Примітка

Випадок в іструкції switch, що містить лише коментар, також призводить до помилки компіляції. Коментарі не є інструкціями і не дозволяють ігнорувати випадок. Тому слід завжди використовувати інструкцію break для ігнорування випадку в switch.

У наступному прикладі розглядається значення Character та визначається, чи представляє воно значення числа в одній із чотирьох мов. Для стислості, в інструкції switch використовуються об’єднані випадки.

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("Цілочисельне значення символу \(numberSymbol) дорівнює \(integerValue).")
} else {
    print("Неможливо визначити цілочисельне значення символу \(numberSymbol).")
}
// Надрукує "Цілочисельне значення символу 三 дорівнює 3."

У цьому прикладі розглядається символ numberSymbol, ти визначається, чи є він латинським, арабським, китайським чи тайським записом числа від 1 до 4. Якщо він співпав з одним із шаблонів, у відповідному випадку інструкції switch опціональній змінній possibleIntegerValue типу Int? присвоюється відповідне цілочисельне значення.

Після завершення інструкції switch, у прикладі використовується прив’язування опціоналу для визначення, чи було знайдено цілочисельне значення. Змінній possibleIntegerValue неявно присвоюється початкове значення nil через те, що вона має опціональний тип. Тому прив’язування опціоналу буде успішним тоді, коли змінній possibleIntegerValue було присвоєно реальне значення в одному із чотирьох випадків інструкції switch.

Оскільки перелічувати всі можливі значення Character у прикладі вище не практично, випадок за замовчанням default покриває всі символи, що не співпали. Цей випадок за замовчанням не повинен виконувати жодної дії, і тому його тіло складається з єдиної інструкції break. Як тільки спрацьовує випадок за замовчанням, інструкція break призводить до виходу з інструкції switch, і виконання коду переходить до наступної інструкції if let.

Перехід до наступного випадку

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

Однак, якщо потрібна саме поведінка інструкцій switch в стилі мови C, її можна увімкнути у кожному конкретному випадку за допомогою ключового слова fallthrough. У наступному прикладі ключове слово fallthrough використовується для створення текстового опису числа.

let integerToDescribe = 5
var description = "Число \(integerToDescribe) є"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " простим числом, і також"
    fallthrough
default:
    description += " цілим."
}
print(description)
// Надрукує "Число 5 є простим числом, і також цілим."

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

Якщо ж значення integerToDescribe не співпадає з жодним зі значень у списку простих значень, перший випадок інструкції switch не виконується. Оскільки більше нема особливих випадків, виконується випадок за замовчанням default.

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

Примітка

Ключове слово fallthrough не перевіряє умову випадку, до виконання якого воно призводить. Ключове слово fallthrough просто змушує виконання коду перейти напряму до інструкцій всередині тіла наступного випадку (або випадку за замовчанням default), як і в стандартній поведінці інструкції switch у мові C.

Іменовані інструкції

У Swift, можна вкладати цикли чи умовні інструкції всередині інших циклів чи умовних інструкцій, щоб створювати складніші структури управління потоком виконання. Однак, всередині циклів та умовних інструкцій можуть бути інструкції break для раннього виходу з них. Таким чином, іноді виникає необхідність у явному позначенні, з якого саме циклу чи умовної інструкції потрібно вийти за допомогою інструкції break. Аналогічно, у випадку кількох вкладених циклів, іноді може бути корисним явно вказувати, що якого з циклів повинна відноситись інструкція continue.

Щоб досягнути цих цілей, інструкції циклів чи умов можна робити іменованими. В умовних інструкціях, ім’я інструкції може використовуватись разом з інструкцією break для завершення її виконання. В інструкції циклу, ім’я інструкції може використовуватись разом з інструкціями break та continue для управління відповідною іменованою інструкцією.

Іменування інструкції відбувається за допомогою вказування імені інструкції на тому ж рядку, де знаходиться перше ключове слово даної інструкції, та двокрапки. Ось приклад даного синтаксису для циклу while, хоча принцип є однаковим для всіх циклів та інструкцій switch:

<ім'я інструкції>: while <умова> {
    <інструкції>
}

У наступному прикладі використовуються інструкції break та continue всередині іменованого циклу while для переробленої версії гри Ліла, з котрою ми ознайомились вище у цьому розділі. Цього разу, у грі є додаткове правило:

  • Для виграшу, потрібно потрапити точно на клітинку 25.

Якщо певний кидок костей призводить до переходу за клітинку 25, потрібно кидати кості знову, допоки не випаде число, що приведе точно на клітинку 25.

Ігрова дошка є такою ж, як і раніше.



Значення змінних finalSquare, board, square, та diceRoll ініціалізуються так само, як і раніше:

let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0

В даній версії гри використовується цикл while з інструкцією switch всередині для реалізації ігрової логіки. Цикл while тепер є іменованою інструкцією з іменем gameLoop, щоб вказати, що він є головним циклом у грі Ліла.

Умовою циклу while є while square != finalSquare, що відображає нове правило щодо потрапляння точно на клітинку 25.

gameLoop: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    switch square + diceRoll {
    case finalSquare:
        // кидок костей пересуне нас на останню клітинку, тому гру закінчено
        break gameLoop
    case let newSquare where newSquare > finalSquare:
        // кидок костей пересуне нас за мені ігрового поля, тому слід кидати кості ще раз
        continue gameLoop
    default:
        // даних хід є коректним, тому слід визначити його ефект (змії, сходи чи нічого)
        square += diceRoll
        square += board[square]
    }
}
print("Гру завершено!")

Кості кидаються на початку кожної ітерації циклу. Замість того, щоб одразу пересунути гравця, тепер використовується інструкція switch, що розглядає результат майбутнього руху та визначає, чи цей рух взагалі можливий:

  • Якщо кидок костей пересуне гравця на останню клітинку, гру завершено. Інструкція break gameLoop передає контроль до першого рядку після циклу while, що й завершує гру.
  • Якщо кидок костей пересуне гравця за межі ігрового поля, рух є неможливим і гравець повинен кидати кості ще раз. Інструкція continue gameLoop завершує поточну ітерацію циклу while та розпочинає наступну ітерацію циклу.
  • В усіх інших випадках, кидок костей призводить до коректного руху. Гравець рухається вперед на diceRoll клітинок, і гра перевіряє наявність змій чи сходів. Після цього ітерація циклу завершується, і контроль переходить до перевірки умови циклу, щоб визначити, чи потрібен ще один хід.

Примітка

Якщо інструкція break би не використовувала ім’я gameLoop, то вона перервала би інструкцію switch, а не цикл while. Використання імені gameLoop робить зрозумілим, яку саме інструкцію потрібно перервати.

Немає строгої необхідності використовувати ім’я gameLoop при виклику continue gameLoop для переходу на наступну ітерацію циклу. Це єдиний цикл у грі, і тому немає невизначеності, до якого із циклів відноситься інструкція continue. Однак, так само немає жодної шкоди у використанні імені gameLoop разом з інструкцією continue. Використання імені робить код послідовнішим (вище ми використовуємо ім’я з інструкцією break), та допомагає легше читати і розуміти код ігрової логіки.

Ранній вихід

Інструкція guard, як і інструкція if, виконує код в залежності від значення булевого виразу. Інструкцію guard слід використовувати тоді, коли певна умова повинна бути істинною (true) для подальшого виконання коду. На відміну від інструкції if, в інструкції guard завжди є пункт else - код всередині пункту else виконується, коли умова хибна (false).

func greet(person: [String: String]) {
    guard let name = person["name"] else {
        return
    }

    print("Вітаємо, \(name)!")

    guard let location = person["city"] else {
        print("Сподіваємось, погода біля вас непогана.")
        return
    }

    print(Сподіваємось, погода у місті \(location) непогана.")
}

greet(person: ["name": "Іван"])
// Надрукує "Вітаємо, Іван!"
// Надрукує "Сподіваємось, погода біля вас непоганаи."
greet(person: ["name": "Марічка", "city": "Київ"])
// Надрукує "Вітаємо, Марічка!"
// Надрукує "Сподіваємось, погода у місті Київ непогана."

Якщо умова всередині інструкції guard виконується, код продовжує виконуватись після закриття фігурної дужки цієї інструкції (}). Всі константи та змінні, що їм було присвоєно значення за допомогою прив’язування опціоналів всередині умови, будуть доступними до кінця блоку коду, де записаний даний guard.

Якщо умова всередині інструкції guard не виконується, відбувається виконання коду всередині інструкції else. Ця гілка коду обов’язково повинна передати контроль до виходу з блоку коду, де записаний даний guard. Вона може це зробити за допомогою інструкцій передачі контролю, таких як return, break, continue, чи throw, або вона може викликати метод чи функцію, з якої нема повернення, таку як fatalError(_:file:line:).

Інструкція guard найкраще підходить для запису передумов певного коду, використання її покращує читабельність коду, якщо порівнювати з аналогічним кодом, записаним за допомогою інструкції if. Вона дозволяє записувати основну гілку коду, що як правило, виконується, без обгортання її в блок else, та дозволяє тримати код, що відноситься до обробки порушених передумов поруч із самими передумовами.

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

У мові Swift є вбудована підтримка перевірки доступності API, котра убезпечує вас від випадкового використання API, що недоступне на тій чи іншій версії тої чи іншої платформи.

Компілятор використовує інформацію про доступність в SDK для перевірки, що всі API використані у вашому коді є доступними на тій версії ОС, що ви вказали у вашому проєкті як мінімальну (deployment target). Якщо
спробувати використати недоступне API, компілятор Swift повідомить про відповідну помилку.

Однак, іноді потрібно використовувати API, що доступні не на всіх версіях ОС, що підтримує ваша програма. Для цього слід використовувати умови доступності в інструкціях if чи guard, щоб певний код виконувався чи не виконувався в залежності від того, чи є певні API доступними під час виконання. Компілятор використовує інформацію про доступність з умови доступності при перевірці, чи всі API у даному блоці коду є доступними.

if #available(iOS 10, macOS 10.12, *) {
    // Використання API, доступних починаючи із iOS 10 та macOS 10.12
} else {
    // Повернення до більш ранніх API iOS та macOS
}

Умови доступності вище вказують, що на iOS, тіло if буде виконуватись тільки на версії 10 або вище; на macOS, тільки на версії 10.12 або вище. Останній аргумент, *, потрібний для того, щоб вказати, що на будь-яких інших платформах, тіло if буде виконуватись на мінімальній версії ОС, вказаній у налаштуваннях проєкту (deployment target).

У загальній формі, умова доступності приймає список платформ на їх версій. Слід вказувати назви платформ, такі, як iOS, macOS, watchOS, та tvOS — повний список можна знайти у розділі Declaration Attributes. Крім основних номерів версій, як iOS 8, можна вказувати також і мінорні номери версій, як iOS 8.3 та macOS 10.10.3.

if #available(<ім'я платформи> <версія>, ..., *) {
    <інструкції для виконання, якщо API доступні>
} else {
    <інструкції для виконання, якщо API недоступні>
}