1. Вступ
Коли ви пишете програму, ви майже завжди розвʼязуєте дві задачі одночасно: «зробити корисне» і «не потонути в деталях». Залежності (dependencies) — це спосіб узяти готові фрагменти функціональності у вигляді бібліотек, щоб не писати все самостійно: парсинг аргументів, форматування, логування, роботу з мережею тощо.
SwiftPM дивиться на залежності прагматично: це не просто «папка з кодом десь поруч», а частина контракту збирання. Ви заздалегідь описуєте в Package.swift, які зовнішні пакети потрібні, а SwiftPM сам завантажує їх і підключає до збирання. В офіційному гайді Swift підкреслюється, що Package.swift — це маніфест, де зберігаються метадані проєкту й залежності.
Важливо вловити ментальну модель: залежність — це не «я написав import — і все запрацювало». У SwiftPM є два різні рівні: рівень збирання (маніфест) і рівень коду (імпорт модуля). І якщо переплутати їх, ви швидко зіткнетеся з помилкою No such module ... — а вона, повірте, любить початківців.
Чому import не означає «підключив бібліотеку»
Якщо ви раніше писали маленькі програми в пісочниці або в одному файлі, мозок звик до простого правила: «хочу щось використовувати — пишу import». SwiftPM змушує мислити трохи інакше: він розділяє підключення залежності як частину збирання і використання залежності у вихідному коді.
Спочатку SwiftPM має дізнатися, що така залежність існує і звідки її брати. Це відбувається в Package.swift у секції dependencies. Потім ваша ціль збирання (у нашому випадку це LibraryCLI) має явно сказати: «я хочу використовувати ось цей модуль або продукт із залежності». Це додається в targets → dependencies. І тільки після цього у .swift-файлах має сенс писати import.
Можна уявити це як пропускний режим в офісі. Package.swift — це ви заздалегідь подали заявку на пропуск (SwiftPM завантажив і підготував бібліотеку). А target.dependencies — це ви внесли цей пропуск до списку тих, кому його справді видали в конкретному відділі. А import — це вже ви зайшли до кабінету і почали користуватися кавомашиною. Якщо заявку подати, але доступ для відділу не оформити, до кабінету ви не потрапите.
Невелика схема (без зайвої романтики, лише правда життя):
flowchart TD
A["Package.swift: dependencies
«Пакет існує, ось URL/версія»"] --> B["Package.swift: target.dependencies
«Ціль LibraryCLI використовує модуль X»"]
B --> C["Код: import X
«Компіляторе, дай мені символи з модуля»"]
C --> D["Ви користуєтеся API бібліотеки"]
2. Терміни: пакет, модуль, залежність
У SwiftPM дуже легко почати плутати терміни, бо в мовленні ми часто кажемо «бібліотека» про все підряд. Давайте акуратно розкладемо по поличках — рівно настільки, щоб упевнено працювати в межах цієї лекції, без забігів у майбутні теми.
Пакет (package) — це те, що ви додаєте за URL у Package.swift. Наприклад, репозиторій на GitHub. У гайді Swift це й показують: додавання .package(url: ..., from: ...) у маніфест.
Модуль (module) — це те, що ви імпортуєте в коді через import Something. Іноді імʼя модуля збігається з імʼям пакета, але зовсім не зобовʼязане збігатися. Саме тому буває ситуація: ви додали пакет, а import усе одно «не знайдено» — бо імпортується модуль, а не URL репозиторію.
Залежність (dependency) — це факт того, що ваш проєкт використовує зовнішній пакет. У SwiftPM залежність спочатку описується в Package.swift, а потім підключається до конкретної цілі.
На рівні сьогоднішньої лекції досить запамʼятати одне практичне правило: якщо ви бачите .package(url: ...), це ще не гарантує, що import запрацює — потрібно ще додати залежність до конкретного .executableTarget(...). Це прямо видно в прикладах офіційного гайду: пакет додають у dependencies, а потім підключають до виконуваної цілі через dependencies: [ .product(...) ].
4. Як додати залежність у Package.swift
Зараз буде важливий момент: ми навчимося читати Package.swift із залежністю так, щоб розуміти, що саме відбувається. При цьому в курсі ми свідомо не робимо акцент на якихось конкретних зовнішніх пакетах — але ви маєте вміти їх підключити, коли настане час.
Нижче приклад форми підключення залежності. Він як мапа метро: допомагає орієнтуватися, але якщо ви вставите туди вигаданий URL — поїзд, звісно, нікуди не поїде.
Псевдоприклад (бо ExampleLib — вигадка):
// Package.swift
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "LibraryCLI",
dependencies: [
.package(url: "https://github.com/example/ExampleLib.git", from: "1.0.0")
],
targets: [
.executableTarget(
name: "LibraryCLI",
dependencies: [
.product(name: "ExampleLib", package: "ExampleLib")
]
)
]
)
Що тут важливо «зчитати очима», а не зубрити.
dependencies: [...] — «У проєкті є зовнішній пакет. SwiftPM, будь ласка, завантаж його».
targets: [ .executableTarget(... dependencies: [...]) ] — «а ось конкретна ціль LibraryCLI використовуватиме продукт або модуль із цього пакета».
І тільки після цього в коді зʼявляється сенс:
// Sources/LibraryCLI/main.swift
import ExampleLib
print("Тепер можна використовувати ExampleLib")
В офіційному матеріалі Swift показують рівно той самий принцип на реальному пакеті (приклад з ASCII-art і подальшим додаванням парсингу аргументів): спочатку змінюємо Package.swift, потім пишемо import у вихідному файлі.
5. Чому в курсі мінімум зовнішніх пакетів
Зараз буде філософія, але дуже прикладна. Початківці часто думають: «зовнішній пакет = швидше розвʼязати задачу». І це правда… у моменті. Але в навчальному курсі нам важливіша не швидкість «тут і зараз», а розуміння «чому так працює» та здатність налагоджувати проблеми.
Є кілька причин, чому ми тримаємо курс майже на стандартній бібліотеці та Foundation.
- Перша причина — передбачуваність. Коли у вас мінімум залежностей, ви майже завжди впевнені, що проблема у вашому коді, а не через невідповідність версій, не у зміненому API пакета і не в тому, що пакет раптово перестав збиратися на вашій платформі. На етапі навчання це критично: ви вчитеся причинно-наслідковим звʼязкам, а не грі «вгадай, хто зламав збирання».
- Друга причина — прозорість. Зовнішня бібліотека часто ховає за красивим API доволі багато концепцій. Це прекрасно в продакшені, але погано в навчанні: замість того щоб зрозуміти, що таке розбір аргументів командного рядка, ви просто пишете два атрибути і магічний run(). У результаті здається, що ви все вмієте, але варто бібліотеку прибрати — і в терміналі починається сумна тиша.
- Третя причина — дисципліна. Кожен зовнішній пакет збільшує поверхню проєкту: більше коду, більше документації, більше потенційних конфліктів імен і більше місць, де можна помилитися. Це як завести вдома ще одного кота: здається, що «та ну, дрібниця», але раптом у вас шерсть у клавіатурі, на клавіатурі, під клавіатурою і десь ще в Package.swift.
- Четверта причина — навички «на майбутнє». Парадоксально, але чим менше залежностей на старті, тим краще ви потім обираєте залежності в реальних проєктах. Ви розумієте, що саме вам потрібно від бібліотеки, які в неї межі і скільки болю вона може принести.
І нарешті, важлива практична деталь: стандартна бібліотека Swift і Foundation уже дають величезний набір інструментів, яких вистачає для «розумного» CLI на доволі тривалий час. У цьому курсі ми будуємо LibraryCLI, і на ранньому етапі нам корисно вміти робити базовий функціонал без зовнішньої магії.
6. Практика: мініпарсер аргументів у LibraryCLI
Зараз ми акуратно поліпшимо наш застосунок LibraryCLI, щоб він відчувався як CLI-інструмент, а не просто «Hello world з амбіціями». Ми зробимо це без зовнішніх залежностей: лише стандартна бібліотека і, за потреби, Foundation.
Читаємо аргументи командного рядка
Почнемо з найпростішого: у Swift є CommandLine.arguments — масив рядків. Перший елемент зазвичай — шлях до програми, тому його зручніше прибрати.
Приклад (Sources/LibraryCLI/main.swift):
import Foundation
let args = Array(CommandLine.arguments.dropFirst())
print("Аргументи LibraryCLI: \\(args)") // наприклад: Аргументи LibraryCLI: ["help"]
Так, це виглядає занадто просто. Але саме так і є: аргументи — це всього лише масив рядків. Далі починається найцікавіше: як перетворити ці рядки на зрозумілі команди й параметри.
Утиліта «значення після прапорця»
Зробимо маленьку функцію, яка шукає прапорець --name і повертає наступний токен, якщо він є. Це не повноцінний парсер, але для початку курсу — чудовий крок: ви бачите логіку, а не лише результат.
Приклад (Sources/LibraryCLI/Args.swift):
import Foundation
func value(after flag: String, in args: [String]) -> String? {
guard let i = args.firstIndex(of: flag) else { return nil }
let next = args.index(after: i)
return next < args.endIndex ? args[next] : nil
}
Зверніть увагу: тут немає жодної магії — лише пошук індексу і перевірка меж масиву. Той самий момент, коли програмування — це не заклинання, а акуратність.
Команда hello і параметр --name
Тепер використаємо цю функцію в main.swift. Зробимо найпростіший сценарій: якщо користувач запускає LibraryCLI hello --name Alex, ми друкуємо привітання. Якщо параметр не передано — беремо значення за замовчуванням.
Приклад (Sources/LibraryCLI/main.swift):
import Foundation
let args = Array(CommandLine.arguments.dropFirst())
let command = args.first ?? "help"
if command == "hello" {
let name = value(after: "--name", in: args) ?? "Анонімний читач"
print("Привіт, \\(name)!") // Привіт, Alex!
} else {
print("Використання: LibraryCLI hello --name <імʼя>")
}
Зверніть увагу: ми вже використовуємо патерн, який траплявся раніше в курсі — «значення за замовчуванням через ??». Він особливо добрий у CLI: користувач може забути аргумент, а програма все одно має поводитися передбачувано.
Ось тут і видно, чому в курсі можна довго жити без зовнішніх пакетів: для базової функціональності цілком вистачає звичайних масивів і рядків.
7. Коли залежність виправдана
Повністю без залежностей жити можна, але не завжди треба. Гарний критерій: залежність виправдана, коли вона знімає з вас рутинну роботу, яку ви або не хочете підтримувати самі, або яку легко зробити неправильно, особливо в питаннях безпеки і коректності.
Наприклад, повноцінний розбір CLI-аргументів із підтримкою --flag=value, скорочень, генерації help, валідації типів і зрозумілих повідомлень про помилки — це справді великий шмат роботи. Тут зовнішня бібліотека може бути дуже доречною. В офіційному гайді Swift показують, що для CLI часто підключають пакет swift-argument-parser, щоб не винаходити «велосипед» на рівному місці.
Але в нашому курсі ми свідомо відкладаємо такі «прискорювачі» на момент, коли ви вже впевнено розумієте базову механіку: що таке аргументи, як улаштовані команди, де точка входу, як працює збирання. Інакше виникає ефект: «ніби працює, але чому — незрозуміло».
Ще один важливий критерій — локалізація ризику. Хороша залежність використовується в невеликій кількості файлів, а не розмазується по всьому проєкту. Тоді, якщо ви вирішите її замінити або прибрати, це не перетворюється на археологічну експедицію по 200 файлах.
І останній критерій — сумісність і стійкість. Навіть не заглиблюючись у теми версіонування (це буде пізніше), корисно мати здорову підозру: якщо бібліотека оновиться, ви маєте розуміти, що саме у вас може зламатися, і як це діагностувати.
8. Типові помилки під час підключення залежностей у SwiftPM
Помилка № 1: «Я написав import, чому модуль не знайдено?»
Це класика. У SwiftPM import працює лише тоді, коли модуль реально підключений до цілі збирання через Package.swift. Якщо ви додали пакет у dependencies, але забули додати його в target.dependencies, компілятор чесно скаже, що модуля немає. Проблема не в Swift, а в тому, що ви не завершили другий крок підключення.
Помилка № 2: плутати імʼя пакета з імʼям модуля для import.
Ви додаєте URL, думаєте, що тепер потрібно import github-repo-name, і отримуєте помилку. Імпортується модуль або продукт, який надає пакет, а його імʼя може відрізнятися. В офіційних прикладах це видно: пакет може називатися одним чином, а модуль, який імпортують, — іншим (наприклад, .product(name: "Figlet", package: "...") і потім import Figlet).
Помилка № 3: намагатися «жити» в Package.swift, як у звичайному Swift-коді застосунку.
Package.swift справді Swift-файл, і там можна написати багато чого… але за змістом це опис збирання. Якщо почати пхати туди логіку застосунку, ви отримаєте дивні помилки і ще дивніші запитання до себе з майбутнього: «Чому я взагалі вирішив, що це хороша ідея?»
Помилка № 4: підключити залежність і одразу побудувати на ній половину проєкту.
Технічно це можливо, але стратегічно — небезпечно. Коли бібліотека стає фундаментом усього застосунку, її оновлення або заміна перетворюється на операцію на відкритому серці без наркозу. На етапі навчання це особливо шкідливо: ви перестаєте бачити власний код.
Помилка № 5: забути, що точка входу має бути одна, а залежності тут ні до чого.
Іноді після додавання пакета люди рефакторять структуру файлів, додають @main, але забувають видалити main.swift. У результаті проєкт не збирається, і здається, що «винна бібліотека». Насправді правило незмінне: або main.swift, або @main-тип — один спосіб на ціль.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ