1. Модель словника
Коли ви вперше бачите ages["Tom"] і замість Int отримуєте Int?, мимоволі виникає думка: «Що? Знову?». Насправді Swift просто піклується про вас — хоч і в доволі суворій формі.
Словник — це структура, у якій ключі не зобов’язані існувати. У масиві індекс майже завжди виглядає «підозрілим», але принаймні зрозуміло, що він має бути в межах діапазону. У словнику ж користувач може попросити ключ "Tom", а у ваших даних є лише "Ann" і "Bob". Що повертати? Нуль? Порожній рядок? «-1»? Усе це — костилі та домовленості, які ламаються вже на першому реальному проєкті.
Тому Swift каже так: «Якщо ключа може не бути, результат читання — Optional. Хочете значення — доведіть, що воно є». Це не занудство, а захист від дуже дорогих помилок.
Наочно модель виглядає так:
flowchart TD
A["Є словник dict"] --> B["Запитуємо dict[key]"]
B --> C{"Ключ існує?"}
C -->|Так| D["Повертаємо .some(значення)"]
C -->|Ні| E["Повертаємо nil (.none)"]
2. Як побачити nil і Optional під час друку
Коли ви друкуєте Optional, іноді виникає відчуття, ніби Swift спеціально хоче, щоб ви побачили Optional(...) і замислилися про сенс життя. У цьому навіть є педагогічний сенс.
Проблема в тому, що print(...) приймає аргументи типу Any, і коли ви передаєте туди optional, Swift ніби нагадує: ви передали Optional без розпакування.
Подивімося на простий приклад:
let ages: [String: Int] = ["Ann": 24, "Bob": 30]
let annAge = ages["Ann"]
let tomAge = ages["Tom"]
print(annAge as Any) // Optional(24)
print(tomAge as Any) // nil
Тут as Any — це не «магія», а просто спосіб сказати компілятору: «так, я свідомо друкую Optional; покажи мені, що там усередині».
3. Розпаковування значень зі словника
if let: базовий і найчитабельніший варіант
Тепер перейдемо до найпрактичнішого. Ви хочете отримати значення, якщо воно є, і нормально обробити ситуацію, якщо його немає. Для цього вам уже знайомий із попередніх тем інструмент — if let.
У контексті словника це читається майже як звичайне українське речення: «якщо за ключем є значення — візьми його».
let prices: [String: Int] = ["coffee": 250, "tea": 180]
if let teaPrice = prices["tea"] {
print("Чай коштує \(teaPrice)") // Чай коштує 180
} else {
print("Чаю немає в прайс-листі")
}
Зверніть увагу на дві речі. По-перше, всередині гілки if змінна teaPrice уже має тип Int, а не Int?. Тобто Swift дозволив вам працювати без Optional там, де ви довели наявність значення. По-друге, код виходить чесним: ми не вдаємо, що ключ існує.
guard let: «перевірив і пішов далі»
Коли ви пишете функції, дуже швидко набридає вкладати логіку в «сходинки» if/else. Особливо якщо у вас є кілька перевірок поспіль: «чи є ключ, чи правильно введено, чи не порожній рядок…» У таких випадках Swift зазвичай пропонує guard let: якщо умови не виконано — виходимо одразу.
Це особливо зручно, коли ви пишете консольний застосунок: користувач увів ключ, а ми хочемо або вивести дані, або показати зрозуміле повідомлення й завершити обробку.
func printPrice(for item: String, in prices: [String: Int]) {
guard let price = prices[item] else {
print("Немає ціни для '\(item)'")
return
}
print("'\(item)' коштує \(price)")
}
printPrice(for: "coffee", in: ["coffee": 250]) // 'coffee' коштує 250
printPrice(for: "cake", in: ["coffee": 250]) // Немає ціни для 'cake'
Зверніть увагу, як читається функція: спочатку перевірка, потім нормальний шлях. Це дуже зменшує шанс зламати логіку.
Не можна порівнювати й рахувати, поки значення — Optional
Дуже типовий сценарій для новачків: ви дістали значення зі словника і одразу хочете порівняти його з числом.
Наприклад, ви хочете перевірити, чи є товару більше нуля:
let stock: [String: Int] = ["pen": 3]
let count = stock["pen"] // Int?
if count > 0 { ... } // ❌ так не можна
Swift не дозволить порівняти Int? і Int, і це добре: порівняння «можливо відсутнього значення» — логічно дивна операція. У спільноті розробників Swift окремо обговорювали, що порівняння Optional може призводити до неочікуваних ефектів, тож краще уникати їх без явного розпакування.
Правильний варіант — розпакувати:
let stock: [String: Int] = ["pen": 3]
if let count = stock["pen"] {
if count > 0 {
print("Є в наявності: \(count)") // Є в наявності: 3
} else {
print("Немає в наявності")
}
} else {
print("Немає такого товару")
}
Так, коду стане трохи більше. Зате логіка чесна: «немає ключа» й «нульовий залишок» — різні випадки.
Перевірка != nil + повторне читання — поганий стиль
Іноді пишуть так: «якщо ключ існує, тоді прочитаємо значення».
// ❌ приклад поганого стилю (але компілюється)
if catalog["b1"] != nil {
print(catalog["b1"]!) // два звернення + force unwrap
}
Тут одразу дві проблеми. Перша — ви читаєте словник двічі, а це зайва робота і зайвий шанс помилитися під час рефакторингу. Друга — ви все одно використовуєте !, тобто якщо код колись зміниться або ключ зміниться, ви отримаєте падіння.
Правильніше — «одним рухом»:
if let title = catalog["b1"] {
print(title)
}
Це той випадок, коли Swift дає вам хороший шаблон, і краще одразу привчити пальці друкувати саме його.
4. Значення за замовчуванням і !: де межа між зручністю та крихкістю
??: коли значення за замовчуванням справді має сенс
Іноді відсутність значення — це не помилка, а нормальна ситуація, де ви хочете підставити розумне значення за замовчуванням. Наприклад, якщо ви показуєте «кількість переглядів», а ключа ще немає, можна вважати, що переглядів 0. Або ви зберігаєте «налаштування гучності», і якщо ключа немає, гучність за замовчуванням — 50.
Ось тут ідеально підходить ??:
let views: [String: Int] = ["home": 120, "profile": 5]
let aboutViews = views["about"] ?? 0
print("Перегляди сторінки «Про нас»: \(aboutViews)") // Перегляди сторінки «Про нас»: 0
Важлива думка: ?? — це не спосіб «полагодити» Optional, а ваша домовленість із логікою. Якщо відсутність ключа й значення 0 для вас мають різний зміст, то ?? 0 буде помилкою дизайну. Наприклад, «вік невідомий» і «вік 0» — це не одне й те саме.
Чому dict[key]! — погана ідея (майже завжди)
Іноді здається: «Та ну, я точно знаю, що ключ є». І рука тягнеться до !:
let age = ages["Ann"]! //
Проблема не в тому, що це «заборонено». Проблема в тому, що ! — це контракт рівня «якщо ключа немає, програма падає». Словник часто використовується як сховище даних із зовнішніх джерел: введення користувача, файл, мережа, результати обчислень. У таких сценаріях «програма падає» — це не нормальна гілка.
Якщо ви все-таки використовуєте !, нехай це буде свідомо: наприклад, у навчальному коді, де ви жорстко контролюєте дані, або в ситуації, де відсутність ключа означає баг розробника, а не помилку користувача. Але в більшості випадків для словника це занадто крихко.
5. Мініпроєкт LibraryMini: пошук книги за ID
Давайте продовжимо розвивати ту саму ідею навчального застосунку: мініконсольну «бібліотеку». Поки без складних типів і структур: до struct ми ще не дійшли, тож працюємо з простими рядками.
Нехай у нас є словник catalog, де ключ — це ID книги, а значення — її назва. Користувач вводить ID, а ми друкуємо назву або повідомляємо, що книги немає.
Спочатку — дані та введення:
import Foundation
let catalog: [String: String] = [
"b1": "Swift для початківців",
"b2": "Алгоритми 101",
"b3": "Пригоди CLI"
]
print("Введіть ID книги (b1/b2/b3):", terminator: " ")
let id = readLine() ?? ""
Тепер найважливіше — безпечне читання за ключем:
if let title = catalog[id] {
print("Назва: \(title)")
} else {
print("Немає книги з ID '\(id)'")
}
Це той момент, коли словник і Optional працюють разом як команда: словник не обіцяє, що ключ існує; Optional змушує вас обробити відсутність.
Якщо хочеться трохи більш «функціонально» — у сенсі «коротше й без розгалуження» — можна підставити значення за замовчуванням, але лише якщо таке значення вам підходить:
let safeTitle = catalog[id] ?? "<невідома книга>"
print("Назва: \(safeTitle)")
Зверніть увагу: тут ми втрачаємо можливість розрізняти «книги справді немає» і «книга є, але називається <невідома книга>» (якби така назва раптом існувала). У реальних застосунках зазвичай корисніший перший варіант із if let, тому що він чесніший.
6. Шпаргалка: який спосіб розпаковування вибрати
Коли варіантів кілька, новачкам важливо не заучувати назви, а бачити сенс. Давайте зафіксуємо це в одній таблиці.
| Ситуація | Що писати | Чому це підходить |
|---|---|---|
| «Якщо значення є — використовую, якщо ні — роблю інше» | |
Найчесніший і найчитабельніший контроль потоку |
| «Я всередині функції, і якщо значення немає, хочу одразу вийти» | |
Ранній вихід робить основний шлях коду прямим |
| «Відсутність значення — це нормально, беру значення за замовчуванням» | |
Прибирає розгалуження, але важливо вибрати змістовне значення за замовчуванням |
| «Я на 100 % впевнений, що ключ є, інакше це баг» | |
Працює, але може впасти — обережно й рідко |
Ще одна тонкість щодо print: якщо ви хочете свідомо друкувати Optional, краще писати as Any, щоб явно показати намір. Це пов’язано з тим, що Optional може неявно потрапляти в Any, і Swift прагне попереджати про такі місця.
7. Типові помилки під час читання зі словника
Помилка № 1: очікувати, що dict[key] повертає «звичайне значення», а не Optional.
Це зазвичай проявляється так: студент пише let age: Int = ages["Ann"] і отримує помилку типів. Лікується не «приведенням типів» і не !, а розумінням моделі: ключ може бути відсутнім, отже результат — Int?, і його потрібно розпакувати через if let, guard let або змістовне ??.
Помилка № 2: лікувати всі Optional через !, бо «так компілюється».
Так, компілюється. Але ціна — падіння програми в будь-який момент, коли ключ раптово не знайдено, а словники часто живуть поруч із введенням користувача. Правильний підхід — спочатку вирішити, що для вас означає відсутність ключа: це помилка, «немає даних», значення за замовчуванням чи привід показати підказку. І вже під це обрати if let / guard let / ??.
Помилка № 3: змішувати «немає ключа» і «значення дорівнює 0/порожньо».
Класика: зберігати вік, а за відсутності ключа підставляти 0, після чого вік 0 починає поводитися як «дані існують». Це логічна помилка, а не синтаксична. Нормальний стиль: якщо відсутність — окремий стан, залишайте nil і обробляйте його розгалуженням, а не підміняйте «немає даних» на «дані дорівнюють нулю».
Помилка № 4: намагатися порівнювати Optional напряму (dict[key] > 0) або робити обчислення, не розпакувавши значення.
Swift забороняє це не зі шкідливості, а щоб ви не виконували беззмістовні операції над «можливою відсутністю». Важливо прийняти правило: порівняння й арифметика виконуються над звичайними значеннями (Int, Double), тож спочатку — розпакування. Додатково корисно пам’ятати, що порівняння Optional може призводити до неочікуваних семантик, тому мова історично посилила обмеження в цій сфері.
Помилка № 5: «перевірив != nil, потім ще раз дістав значення».
Такий код майже завжди виходить більш крихким: ви робите два читання, а ще часто закінчуєте !. Набагато простіше й безпечніше одразу використовувати if let, який одночасно перевіряє наявність і дає вам не-Optional значення в тілі гілки.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ