Link Search Menu Expand Document

Розширені оператори

Окрім операторів, які описані у розділі Базові оператори, Swift має певну кількість розширених операторів, для виконання складніших маніпуляції зі значеннями. Вони охоплюють побітові оператори та бітові зсуви, з якими ви можете бути знайомими з мови C та Objective-C.

На відміну від арифметичних операторів у мові C, арифметичні оператори у Swift за замовчанням не дозволяють переповнення змінних. Щоб увімкнути поведінку переповнення, у Swift використовується другий набір арифметичних операторів, що за замовчанням дозволяють переповнення, як, наприклад, оператор додавання з переповненням (&+). Усі ці оператори з переповненням починаються з амперсанда (&).

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

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

Побітові оператори

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

Swift підтримує усі побітові оператори, що є у мові C, котрі описані нижче.

Оператор побітового НЕ

Оператор побітового НЕ (~) інвертує усі біти у числі:

Оператор побітового НЕ є префіксним оператором, і пишеться безпосередньо перед значенням, над яким він оперує, без пробілу:

let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits  // дорівнює 11110000

Цілі числа типу UInt8 мають вісім біт та можуть зберігати значення від 0 до 255. У цьому прикладі ініціалізовано ціле типу UInt8 з бінарним значенням 00001111, в якому перші чотири біти дорівнюють нулю, а другі чотири біти дорівнюють одиниці. Це є еквівалентом десяткового значення 15.

Оператор побітового НЕ далі використовується для створення нової константи на ім’я invertedBits, котра дорівнює initialBits, однак з інвертованими бітами. Нулі стають одиницями, а одиниці стають нулями. Значення invertedBits дорівнює 11110000, що є еквівалентом беззнакового десяткового значення 240.

Оператор побітового І

Оператор побітового І (&) поєднує біти двох чисел. Він повертає нове число, чиї біти дорівнюють 1 лише тоді, коли відповідні біти дорівнюють 1 в обох вхідних числах.

У прикладі нижче, значення firstSixBits та lastSixBits мають чотири біти посередині, що дорівнюють 1. Оператор побітового І поєднує їх для створення числа 00111100, що є еквівалентом беззнакового десяткового значення 60:

let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8  = 0b00111111
let middleFourBits = firstSixBits & lastSixBits  // дорівнює 00111100

Оператор побітового АБО

Оператор побітового АБО (|) порівнює біти двох чисел. Оператор повертає нове число, чиї біти дорівнюють 1 у позиціях, де біти хоча б одного із двох вхідних чисел дорівнюють 1:

У прикладі нижче, значення someBits та moreBits мають різні біти, що дорівнюють 1. Оператор побітового АБО поєднує їх для створення числа 11111110, що є еквівалентом беззнакового десяткового числа 254:

let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits  // дорівнює 11111110

Оператор побітового виключного АБО (XOR)

Оператор побітового виключного АБО, або XOR (від “exclusive OR”) (^), поєднує біти двох чисел. Оператор повертає нове число, чиї біти дорівнюють 1 там, де біти вхідних чисел відрізняються, та 0 там, де біти вхідних чисел співпадають:

У прикладі нижче, значення firstBits та otherBits мають одиничні біти у різних позиціях. Оператор побітового виключного АБО присвоює результату 1 у двох позиціях, котрі відрізняється. Решта біт firstBits та otherBits співпадають, і тому в цих позиціях результат має значення 0:

let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits  // дорівнює 00010001

Оператори побітового зсуву вліво та вправо

Оператор побітового зсуву вліво (<<) та оператор побітового зсуву вправо (>>) пересувають усі біти числа вліво чи вправо на певну кількість позицій, відповідно до правил нижче.

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

Поведінка зсуву беззнакових цілих

Поведінка зсуву для беззнакових цілих є наступною:

  1. Існуючі біти пересуваються вліво чи вправо на задану кількість позицій.
  2. Усі біти, що зсуваються за межі сховища цілого числа відкидаються.
  3. Вивільнене місце зліва чи справа після оригінальних бітів заповнюється нулями.

Даний підхід є відомий як логічний зсув.

Ілюстрація нижче демонструє результати зсуву 11111111 << 1 (тобто число 11111111 зсунуте на 1 позицію вліво), та зсуву 11111111 >> 1 (тобто 11111111 зсунуте на 1 позицію вправо). Сині біти зсовуються, сірі – відкидаються, а помаранчеві – вставляються:

Ось як побітовий зсув виглядає у коді мовою Swift:

let shiftBits: UInt8 = 4   // 00000100 у двійковій системі числення
shiftBits << 1             // 00001000
shiftBits << 2             // 00010000
shiftBits << 5             // 10000000
shiftBits << 6             // 00000000
shiftBits >> 2             // 00000001

Побітовий зсув можна використовувати для кодування та декодування значень інших типів даних:

let pink: UInt32 = 0xCC6699                   // рожевий колір у RGB
let redComponent = (pink & 0xFF0000) >> 16    // червона компонента дорівнює 0xCC, або 204
let greenComponent = (pink & 0x00FF00) >> 8   // зелена компонента дорівнює 0x66, або 102
let blueComponent = pink & 0x0000FF           // синя компонента дорівнює 0x99, або 153

У даному прикладі константа типу UInt32 на ім’я pink використовується для зберігання рожевого кольору у форматі CSS. Значення кольору CSS #CC6699 записується у Swift як шістнадцяткове число 0xCC6699. Цей колір потім декомпонується на його червону (СС), зелену (66) та синю (99) компоненти за допомогою оператору побітового І (&) та оператору побітового зсуву вправо (>>).

Червона компонента отримується за допомогою виконання побітового І між числами 0xCC6699 та 0xFF0000. Нулі у 0xFF0000 фактично “маскують” другий та третій байти у 0xCC6699, змушуючи частину 6699 бути проігнорованою та залишаючи результатом 0xCC0000.

Далі це число зсовується на 16 позицій вправо (>> 16). Кожна пара символів у шістнадцятковому числі використовує 8 біт, тому зсув на 16 позицій вправо перетворить 0xCC0000 на 0x0000CC. Це те ж саме, що число 0xCC, котре в десятковому записі має вигляд “204”.

Аналогічно, зелена компонента отримується за допомогою виконання побітового І між числами 0xCC6699 та 0x00FF00, котрі дають вихідне значення 0x006600. Це значення потім зсувається на 8 позицій вправо, що дає значення 0x66, або 102 в десятковому записі.

Нарешті, синя компонента отримується за допомогою виконання побітового І між числами 0xCC6699 та 0x0000FF, що дає вихідне значення 0x000099. Немає потреби зсувати це число вправо, оскільки 0x000099 вже дорівнює 0x99, або 153 в десятковому записі.

Поведінка зсуву знакових цілих

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

У знакових цілих перший біт використовується для зберігання знаку числа, тому він має назву “знаковий біт” та визначає, чи є число додатнім або від’ємним. Якщо знаковий біт дорівнює 0 – число вважається додатнім, якщо 1 – від’ємним.

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

bitshiftSignedFour_2x

Знаковий біт дорівнює 0 (означаючи “додатне”), а сім бітів значення формують число 4, записане у бінарній формі.

Від’ємні числа зберігаються в інший спосіб. Вони зберігаються у вигляді їх абсолютного значення, віднятого від 2 в степені n, де n – це кількість бітів значення. Восьмибітове знакове число має сім бітів значення, тому використовується 2 в степені 7, тобто 128.

Ось як виглядають біти всередині числа -4 типу Int8:

bitshiftSignedMinusFour_2x

Цього разу, знаковий біт дорівнює 1 (означаючи “від’ємне”), а сім біт значення містять бінарне значення 124 (тобто 128 - 4):

bitshiftSignedMinusFourValue_2x

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

По-перше, щоб додати -1 до -4, достатньо виконати стандартне бінарне додавання на всіх восьми бітах (включно зі знаковим бітом), та відкинути все, що не вміщується у 8 біт по завершенню операції:

bitshiftSignedAddition_2x

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

bitshiftSigned_2x

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

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

Оператори переповнення

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

Наприклад, цілочисельний тип Int16 може зберігати знакові цілочисельні значення між -32768 та 32767. Спроба присвоїти константі чи змінній типу Int16 значення за межами цього діапазону призведе до помилки:

var potentialOverflow = Int16.max
// potentialOverflow дорівнює 32767, що є максимальним значенням, яке може втримати Int16
potentialOverflow += 1
// це призводить до помилки

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

Однак, коли вам спеціально потрібна поведінка переповнення, щоб обрізати число до доступних бітів, можна увімкнути її замість провокування помилок. Swift має три арифметичних оператори переповнення, що дозволяють поведінку переповнення для цілочисельних розрахунків. Ці оператори починаються із символу (&):

  • Додавання з переповненням (&+)
  • Віднімання з переповненням (&-)
  • Множення з переповненням (&*)

Переповнення значень

Числа можуть переповнюватись як у додатному, так і у від’ємному напрямках.

Ось приклад того, що трапляється, коли беззнакове ціле переповнюється у додатному напрямку, за допомогою оператору додавання з переповненням: (&+):

var unsignedOverflow = UInt8.max
// unsignedOverflow дорівнює 255, що є максимальним значенням типу UInt8
unsignedOverflow = unsignedOverflow &+ 1
// unsignedOverflow тепер дорівнює 0

Змінну unsignedOverflow ініціалізовано із максимальним значенням типу UInt8 (255, або 11111111 у бінарному представленні). До нього додається число 1 за допомогою оператору додавання з переповненням (&+). Це збільшує бінарне представлення до розміру, що на 1 більше за розмір числа, що вміщається у типі UInt8, призводячи до переповнення його меж, як показано на діаграмі нижче. Значення, що вміщається в межах UInt8 після переповнення є 00000000, або нуль.

overflowAddition_2x

Щось аналогічне відбувається, коли беззнакове ціло переповнюється у від’ємному напрямку. Ось приклад використання оператору віднімання із переповненням (&-):

var unsignedOverflow = UInt8.min
// unsignedOverflow дорівнює 0, що є мінімальним значенням типу UInt8
unsignedOverflow = unsignedOverflow &- 1
// unsignedOverflow тепер дорівнює 255

Мінімальним значенням, що лежить в межах UInt8 є нуль, або 00000000 у двійковому представленні. Якщо відняти 1 від 00000000 використовуючи оператор віднімання із переповненням (&-), число переповниться та обріжеться до 11111111, або 255 у десятковому записі.

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

var signedOverflow = Int8.min
// signedOverflow дорівнює -128, що є мінімальним значенням типу Int8
signedOverflow = signedOverflow &- 1
// signedOverflow тепер дорівнює 127

Мінімальним значенням типу Int8 є -128, або 10000000 у бінарній формі. Віднімаючи 1 від цього бінарного числа за допомогою оператору з переповненням дає бінарне значення 01111111, що перемикає знаковий біт та дає додатне 127, максимальне додатне значення, що може міститись у Int8.

overflowSignedSubtraction_2x

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

Черговість та асоціативність

Черговість операторів дає одним операторам пріоритет над іншими; ці оператори застосовуються першими.

Асоціативність операторів визначає, як оператори однієї черговості групуються разом: зліва направо чи справа наліво. Слід думати про це як “вони асоційовані з виразом зліва від них,” або “вони асоційовані з виразом справа від них”.

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

2 + 3 % 4 * 5
// це дорівнює 17

Якщо читати цей вираз строго зліва направо, можна очікувати, що вираз буде обчислено наступним чином:

  • 2 плюс 3 дорівнює 5
  • остача від ділення 5 на 4 дорівнює 1
  • 1 помножити на 5 дорівнює 5

Однак, фактична відповідь є 17, не 5. Оператори з вищою черговістю опрацьовуються перед операторами з нижчою черговістю. У Swift, як і у C, оператор остачі від ділення (%) та оператор множення мають вищу черговість, ніж оператор додавання (+). У результаті вони обидва опрацьовуються перед додаванням.

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

2 + ((3 % 4) * 5)

(3 % 4) дорівнює 3, тому це є еквівалентом:

2 + (3 * 5)

(3 * 5) дорівнює 15, тому це є еквівалентом:

2 + 15

Дане обчислення видає остаточну відповідь 17.

Детальніше з операторами, що є у стандартній бібліотеці Swift, включно зі списком груп черговості операторів, та налаштувань асоціативності, можна ознайомитись за посиланням Operator Declarations.

Note

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

Методи-оператори

Класи та структури можуть мати їх власні реалізації існуючих операторів. Це відомо як перевантаження існуючих операторів.

У прикладі нижче показано, як реалізувати арифметичний оператор додавання (+) для власної структури. Арифметичний оператор додавання є бінарним оператором, оскільки він оперує над двома аргументами, та вважається інфіксним, оскільки він пишеться посередині між двома аргументами.

У прикладі визначено структуру Vector2D для моделювання двовимірного вектора (x, y), та визначення методу-оператору для додавання двох екземплярів структури Vector2D:

struct Vector2D {
    var x = 0.0, y = 0.0
}

extension Vector2D {
    static func + (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
}

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

У цій реалізації, вхідні параметри іменуються як left (лівий) та right (правий), щоб представляти екземпляри Vector2D, що будуть відповідно по ліву та праву сторону від оператора +. Метод повертає новий екземпляр Vector2D, чиї властивості x та y ініціалізуються сумами відповідних властивостей x та y кожного з двох екземплярів Vector2D, що додаються.

Метод типу можна використовувати як інфіксний оператор між існуючими екземплярами Vector2D:

let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector є екземпляром Vector2D зі значеннями (5.0, 5.0)

У даному прикладі додаються вектори (3.0, 1.0) та (2.0, 4.0), що дає у результаті вектор (5.0, 5.0), як показано на зображенні нижче.

vectorAddition_2x

Префіксні та постфіксні оператори

У прикладі вище демонструється власна реалізація бінарного інфіксного оператора. Класи та структури також можуть мати реалізації стандартних унарних операторів. Унарні оператори оперують над одним аргументом. Вони бувають префіксними, коли вони пишуться перед своїм аргументом (наприклад, -a), та постфіксними, коли вони пишуться після свого аргументу (наприклад, b!).

Префіксні та постфіксні унарні оператори реалізовуються за допомогою модифікаторів prefix та postfix відповідно, які вказуються перед ключовим словом func в оголошенні методу-оператора:

extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}

У прикладі вище реалізовано оператор унарного мінуса (-a) для екземплярів Vector2D. Оператор унарного мінуса є префіксним оператором, і тому цей метод цей метод позначено модифікатором prefix.

Для простих числових значень, унарний мінус перетворює додатні числа на їх від’ємні еквіваленти та навпаки. Відповідна реалізація для екземпляра Vector2D виконує цю операцію над кожною з властивостей x та y.

let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative є екземпляром Vector2D зі значеннями (-3.0, -4.0)
let alsoPositive = -negative
// alsoPositive є екземпляром Vector2D зі значеннями (3.0, 4.0)

Складені оператори присвоєння

Складені оператори присвоєння поєднують присвоєння з іншою операцією. Наприклад, оператор додавання з присвоєнням (+=) поєднує додавання із присвоєнням в єдиній операції. Лівий параметр складеного оператора присвоєння повинен бути двонаправленим параметром (поміченим модифікатором inout), оскільки значення цього параметру буде змінено прямо у ході операції.

У прикладі нижче реалізовано метод-оператор додавання з присвоєнням для екземплярів Vector2D:

extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        left = left + right
    }
}

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

var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original тепер має значення (4.0, 6.0)

Примітка

Неможливо перевантажити стандартниий оператор присвоєння (=). Перевантажити можна тільки складені оператори присвоєння. Аналогічно, неможливо перевантажити умовний тернарний оператор (a ? b : c).

Оператори рівності

За замовчанням, власні класи та структури не мають реалізації операторів еквівалентності, якими є оператор рівності (==), та оператор нерівності (!=). Зазвичай реалізовується оператор рівності ==, і використовується реалізація оператору нерівності != зі стандартної бібліотеки, котра інвертує результат оператору ==. Є два способи реалізувати оператор ==: реалізувати його самотужки, або, для багатьох типів, можна попросити Swift синтезувати реалізацію за вас. В обох випадках, потрібно підпорядкувати тип протоколу Equatable зі стандартної бібліотеки.

Реалізувати оператор == можна в той же спосіб, що й інші інфіксні оператори:

extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
}

У прикладі вище реалізовано оператор == для перевірки двох екземплярів Vector2D на рівність. У контексті Vector2D, “рівні” означає “обидва екземпляри мають однакові значення x та y”, і тому ця логіка використовується в реалізації оператору.

Тепер цей оператор можна використовувати для перевірки екземплярів Vector2D на рівність:

let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
    print("Ці два вектори рівні між собою.")
}
// Надрукує "Ці два вектори рівні між собою."

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

Власні оператори

На додачу до стандартних операторів Swift, можна оголошувати та реалізовувати свої власні оператори. З повним списком символів, які можна використовувати для створення власних операторів, можна ознайомитись у секції Оператори.

Нові оператори оголошуються на глобальному рівні за допомогою ключового слова operator, та модифікаторів prefix, infix або postfix:

prefix operator +++

У прикладі вище визначено префіксний оператор на ім’я +++. Цей оператор не має існуючої семантики у Swift, і тому ми можемо надати йому власну семантику нижче у конкретному контексті роботи з екземплярами Vector2D. Для цілей даного прикладу, +++ вважатиметься новим оператором “префіксного подвоєння”. Він подвоюватиме значення x та y екземпляру Vector2D, додаючи вектор сам до себе за допомогою визначеного вище оператора додавання з присвоєнням. Щоб реалізувати оператор +++, до структури Vector2D ми додаємо метод на ім’я +++:

extension Vector2D {
    static prefix func +++ (vector: inout Vector2D) -> Vector2D {
        vector += vector
        return vector
    }
}

var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled тепер має значення (2.0, 8.0)
// afterDoubling тепер також має значення (2.0, 8.0)

Черговість власних інфіксних операторів

Кожен власний інфіксний оператор належить до певної групи черговості. Група черговості оператору визначає порядок його виконання у виразі з іншими інфіксними операторами, так само як і асоціативність оператору. У секції Черговість та асоціативність детально пояснено, як ці характеристики впливають та взаємодію інфіксного оператора з іншими інфіксними операторами.

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

У наступному прикладі визначено новий інфіксний оператор на ім’я +-, котрий належить до групи черговості AdditionPrecedence:

infix operator +-: AdditionPrecedence
extension Vector2D {
    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector є екземпляром Vector2D зі значеннями (4.0, -2.0)

Цей оператор додає значення x, та від значення y лівого вектора віднімає значення y правого. Оскільки даний оператор в певному сенсі є оператором “додавання”, йому дається така ж група черговості, як і в інфіксних операторах додавання, таких як + та -. Детальніше зі списком операторів стандартної бібліотеки Swift, включно із повним списком груп черговості та налаштувань асоціативності, можна ознайомитись у секції Оголошення операторів.

Примітка

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

Будівельники результатів

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

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

protocol Drawable {
    func draw() -> String
}
struct Line: Drawable {
    var elements: [Drawable]
    func draw() -> String {
        return elements.map { $0.draw() }.joined(separator: "")
    }
}
struct Text: Drawable {
    var content: String
    init(_ content: String) { self.content = content }
    func draw() -> String { return content }
}
struct Space: Drawable {
    func draw() -> String { return " " }
}
struct Stars: Drawable {
    var length: Int
    func draw() -> String { return String(repeating: "*", count: length) }
}
struct AllCaps: Drawable {
    var content: Drawable
    func draw() -> String { return content.draw().uppercased() }
}

Протокол Drawable визначає вимогу здатності до малювання чогось, на кшталт рядка або фігури: підпорядкований тип повинен реалізовувати метод draw(). Структура Line репрезентує однорядковий малюнок і слугує найвищим контейнером для більшості малюнків. Щоб намалювати рядок, структура Line викликає метод draw() на кожному з компонентів рядка, після чого конкатенує рядки-результати цих викликів у єдиний рядок. Структура Text обгортає рядок, щоб зробити його частиною малюнка. Структура AllCaps обгортає та модифікує інший малюнок, перетворюючи будь-який текст у ньому до верхнього регістру.

Створити малюнок з цими типами, викликаючи їх ініціалізатори, можна:

let name: String? = "Ravi Patel"
let manualDrawing = Line(elements: [
     Stars(length: 3),
     Text("Hello"),
     Space(),
     AllCaps(content: Text((name ?? "World") + "!")),
     Stars(length: 2),
])
print(manualDrawing.draw())
// Надрукує "***Hello RAVI PATEL!**"

Цей код працює, однак він трошки незручний. Глибоко вкладені круглі дужки після AllCaps важко читати. Логіка використовувати "World", коли name дорівнює nil, повинна реалізовуватись у цьому ж рядку за допомогою оператора ??, що було б складно з будь-чим складнішим. Якщо вам знадобиться включити інструкцію switch або for, щоб побудувати частину малюнка, то це буде неможливо втілити. Будівельники результатів дозволяють вам переписати код у подібних випадках так, щоб він виглядав як звичайний код мовою Swift.

Щоб оголосити будівельник результатів, слід вказати атрибут @resultBuilder під час оголошення типу. Наприклад, цей код визначає будівельник результатів на ім’я DrawingBuilder, котрий дозволяє вам користуватись декларативним синтаксисом для опису малюнків:

@resultBuilder
struct DrawingBuilder {
    static func buildBlock(_ components: Drawable...) -> Drawable {
        return Line(elements: components)
    }
    static func buildEither(first: Drawable) -> Drawable {
        return first
    }
    static func buildEither(second: Drawable) -> Drawable {
        return second
    }
}

Структура DrawingBuilder визначає три методи, що реалізовують частини синтаксису будівельника результатів. Метод buildBlock(_:) додає підтримку написання послідовності рядків у блоці коду. Він об’єднує компоненти у цьому блоці в екземпляр Line. Методи buildEither(first:) та buildEither(second:) додають підтримку інструкції if-else.

Тепер можна застосувати атрибут @DrawingBuilder до параметра функції, котрий перетворить замикання, котре передається до функції, на значення, котре будівельник результатів створить з цього замикання. Наприклад:

func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
    return content()
}
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
    return AllCaps(content: content())
}

func makeGreeting(for name: String? = nil) -> Drawable {
    let greeting = draw {
        Stars(length: 3)
        Text("Hello")
        Space()
        caps {
            if let name = name {
                Text(name + "!")
            } else {
                Text("World!")
            }
        }
        Stars(length: 2)
    }
    return greeting
}
let genericGreeting = makeGreeting()
print(genericGreeting.draw())
// Надрукує "***Hello WORLD!**"

let personalGreeting = makeGreeting(for: "Ravi Patel")
print(personalGreeting.draw())
// Надрукує "***Hello RAVI PATEL!**"

Функція makeGreeting(for:) приймає параметр name та використовує його, щоб намалювати персоналізоване привітання. Функції draw(_:) та caps(_:) обидві приймають єдине замикання як аргумент, котрий позначено атрибутом @DrawingBuilder. Викликаючи ці функції, ви послуговуєтесь спеціальним синтаксисом, котрий визначає DrawingBuilder. Swift перетворює це декларативне описання малюнка у послідовність викликів методів DrawingBuilder, щоб побудувати значення, що передається як аргумент функції. Наприклад, Swift перетворює виклик caps(_:) у цьому прикладі на код схожий на наступний:

let capsDrawing = caps {
    let partialDrawing: Drawable
    if let name = name {
        let text = Text(name + "!")
        partialDrawing = DrawingBuilder.buildEither(first: text)
    } else {
        let text = Text("World!")
        partialDrawing = DrawingBuilder.buildEither(second: text)
    }
    return partialDrawing
}

Swift перетворює блок if-else на виклики методів buildEither(first:) та buildEither(second:). Хоч ви й не викликаєте ці методи у вашому власному коді, приклад результату трансформації вище дозволяє легше побачити, як Swift трансформує ваш код при використанні синтаксису DrawingBuilder.

Для підтримки написання циклів for у спеціальному синтаксисі малювання, слід додати метод buildArray(_:).

extension DrawingBuilder {
    static func buildArray(_ components: [Drawable]) -> Drawable {
        return Line(elements: components)
    }
}
let manyStars = draw {
    Text("Stars:")
    for length in 1...3 {
        Space()
        Stars(length: length)
    }
}

У коді вище, цикл for створює масив малюнків, а метод buildArray(_:) перетворює цей масив у екземпляр Line.

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