Практика 16. Практика к лекции 14
Цель: закрепить навыки работы с экосистемой Swift на Linux — создание пакетов (SPM), работа с Foundation и Codable, построение CLI-утилит, портирование Python-кода и знакомство с серверной разработкой на Vapor.
Рекомендации по выполнению:
- Все задания выполняются на Linux (без Xcode, UIKit/SwiftUI).
- Используйте
swift package initдля создания пакетов. - Компилируйте через
swift build, запускайте черезswift run. - Тесты запускайте через
swift test. - Для заданий с Vapor убедитесь, что установлен Swift 5.9+ и доступен менеджер пакетов.
A. Разминка — Swift Package Manager
- Создайте Swift-пакет
Greeting:swift package init --name Greeting --type library;- в модуле реализуйте функцию
greet(name: String) -> String, возвращающую"Привет, \(name)! Добро пожаловать в Swift."; - в файле
Tests/GreetingTests/GreetingTests.swiftнапишите тест, проверяющий результат для нескольких имён; - запустите
swift buildиswift test, убедитесь, что всё проходит.
Пример ожидаемого поведения:
public func greet(name: String) -> String { "Привет, \(name)! Добро пожаловать в Swift."}
// Tests/GreetingTests/GreetingTests.swiftimport XCTest@testable import Greeting
final class GreetingTests: XCTestCase { func testGreet() { XCTAssertEqual(greet(name: "Анна"), "Привет, Анна! Добро пожаловать в Swift.") }
func testGreetEmpty() { XCTAssertTrue(greet(name: "").contains("Привет")) }}- Добавьте в пакет
Greetingвторой таргет — исполняемыйGreetingCLI, который принимает имя из аргументов командной строки и вызываетgreet(name:). ОбновитеPackage.swift:- добавьте
.executableTarget(name: "GreetingCLI", dependencies: ["Greeting"]); - запустите через
swift run GreetingCLI Мир.
- добавьте
Пример структуры Package.swift:
import PackageDescription
let package = Package( name: "Greeting", targets: [ .target(name: "Greeting"), .executableTarget(name: "GreetingCLI", dependencies: ["Greeting"]), .testTarget(name: "GreetingTests", dependencies: ["Greeting"]), ])B. CLI-утилита с JSON
- Напишите CLI-утилиту
jsonformat, которая:- принимает путь к JSON-файлу из аргументов командной строки;
- читает файл через
FileManager/Data(contentsOf:); - парсит JSON через
JSONSerialization; - выводит отформатированный JSON с отступами (
.prettyPrinted); - при ошибке (файл не найден, невалидный JSON) выводит понятное сообщение в
stderr.
Пример использования:
$ echo '{"name":"Swift","version":5.9,"features":["async","actors"]}' > test.json$ swift run jsonformat test.json{ "name": "Swift", "version": 5.9, "features": [ "async", "actors" ]}- Расширьте
jsonformat: добавьте флаг--compact, выводящий JSON в одну строку (без пробелов), и флаг--sort-keysдля сортировки ключей. Реализуйте разбор аргументов вручную (без внешних зависимостей) или с помощьюswift-argument-parser.
C. Codable и работа с данными
- Создайте
Codable-структуруConfig:
struct Config: Codable { let host: String let port: Int let debug: Bool let allowedOrigins: [String]}- Напишите функцию
loadConfig(from path: String) throws -> Config, читающую JSON-файл и декодирующую его черезJSONDecoder; - напишите функцию
saveConfig(_ config: Config, to path: String) throws, сохраняющую конфиг обратно в файл черезJSONEncoderс.prettyPrinted; - обработайте все ошибки через
do-catchбез force unwrapping; - протестируйте с файлом
config.json.
Пример JSON (config.json):
{ "host": "0.0.0.0", "port": 8080, "debug": true, "allowedOrigins": ["http://localhost:3000", "https://example.com"]}- Создайте
enumс ассоциированными значениями и сделайте егоCodable:
enum Shape: Codable { case circle(radius: Double) case rectangle(width: Double, height: Double) case triangle(a: Double, b: Double, c: Double)}- Напишите массив
[Shape], закодируйте в JSON и декодируйте обратно; - убедитесь, что данные совпадают после round-trip.
D. Портирование Python → Swift
- Перепишите следующий Python-скрипт на Swift. Сравните читаемость, типобезопасность и обработку ошибок:
import jsonimport sysfrom collections import Counter
def analyze_log(path: str) -> dict: """Анализирует лог-файл: считает уровни логов и находит самое частое сообщение.""" levels = Counter() messages = Counter()
with open(path) as f: for line in f: parts = line.strip().split(" ", 2) if len(parts) < 3: continue timestamp, level, message = parts levels[level] += 1 messages[message] += 1
return { "total_lines": sum(levels.values()), "levels": dict(levels), "top_message": messages.most_common(1)[0] if messages else None }
if __name__ == "__main__": if len(sys.argv) < 2: print("Использование: python analyze.py <путь>", file=sys.stderr) sys.exit(1) result = analyze_log(sys.argv[1]) print(json.dumps(result, indent=2, ensure_ascii=False))Формат лог-файла (каждая строка):
2025-01-15T10:30:00 INFO Сервер запущен2025-01-15T10:30:05 ERROR Ошибка подключения к БД2025-01-15T10:30:10 INFO Повторное подключение2025-01-15T10:30:15 WARN Медленный запросТребования к Swift-версии:
- используйте
Codableдля вывода результата в JSON; - обработайте ошибки чтения файла через
throws; - используйте
Dictionaryи сортировку вместоCounter.
E. Комплексная CLI-утилита
- Создайте SPM-пакет
TaskManager— CLI-приложение для управления задачами:- Хранение данных в JSON-файле (
tasks.json); - Команды (через аргументы командной строки):
add "Название задачи"— добавить задачу;list— показать все задачи;done <id>— отметить задачу выполненной;remove <id>— удалить задачу;
- Модель данных:
- Хранение данных в JSON-файле (
struct TaskItem: Codable, Identifiable { let id: UUID var title: String var isDone: Bool let createdAt: Date}- Требования:
Codableдля сериализации;- обработка ошибок (файл не найден — создать новый; некорректный ID — сообщить);
- форматированный вывод списка с номерами, статусами и датами;
- тесты для логики добавления/удаления/завершения.
Пример использования:
$ swift run TaskManager add "Изучить SPM"Задача добавлена: Изучить SPM (id: 1a2b3c...)
$ swift run TaskManager add "Написать тесты"Задача добавлена: Написать тесты (id: 4d5e6f...)
$ swift run TaskManager list # Статус Задача Создана 1 [ ] Изучить SPM 2025-06-15 2 [ ] Написать тесты 2025-06-15
$ swift run TaskManager done 1a2b3cЗадача отмечена как выполненная: Изучить SPM
$ swift run TaskManager list # Статус Задача Создана 1 [✓] Изучить SPM 2025-06-15 2 [ ] Написать тесты 2025-06-15F. Мини-проект — серверная разработка (Vapor)
- Создайте минимальный Vapor-сервер:
swift package init --name MiniServer --type executable;- добавьте зависимость Vapor в
Package.swift:
import PackageDescription
let package = Package( name: "MiniServer", platforms: [.macOS(.v13)], dependencies: [ .package(url: "https://github.com/vapor/vapor.git", from: "4.89.0"), ], targets: [ .executableTarget( name: "MiniServer", dependencies: [ .product(name: "Vapor", package: "vapor"), ] ), ])- Реализуйте маршруты:
GET /— возвращает"Добро пожаловать в MiniServer!";GET /time— возвращает текущее время в JSON:{"time": "2025-06-15T10:30:00"};POST /echo— принимает JSON{"message": "..."}и возвращает его обратно;GET /hello/:name— возвращает персональное приветствие.
- Запустите сервер и протестируйте через
curl.
Пример тестирования:
$ curl http://localhost:8080/Добро пожаловать в MiniServer!
$ curl http://localhost:8080/time{"time":"2025-06-15T10:30:00"}
$ curl -X POST http://localhost:8080/echo \ -H "Content-Type: application/json" \ -d '{"message":"Привет, Vapor!"}'{"message":"Привет, Vapor!"}
$ curl http://localhost:8080/hello/SwiftПривет, Swift!- Расширьте сервер из задания 9:
- Добавьте модель
Item: Contentс полямиid: UUID?,name: String,price: Double; - Реализуйте in-memory CRUD:
GET /items— список всех товаров;POST /items— создать товар;GET /items/:id— получить товар по ID;DELETE /items/:id— удалить товар.
- Добавьте middleware для логирования запросов.
G. Критерии оценивания
- SPM: создание пакета, таргеты, тесты (
swift build,swift test): 0–4 балла Codable, JSON, CLI-утилиты: 0–4 балла- Портирование Python → Swift, качество кода: 0–4 балла
- Комплексная утилита или серверный мини-проект: 0–4 балла
Максимум: 16 баллов. Бонус до +2 за дополнительные задания.
H. Дополнительно (по желанию)
- Добавьте в
TaskManagerподкомандуexport --format csv|jsonдля экспорта задач в разные форматы. - Подключите пакет
swift-argument-parserдля парсинга аргументов командной строки вместо ручного разбора. - Добавьте в Vapor-сервер базу данных через Fluent (SQLite) вместо in-memory хранения.
- Напишите
Dockerfileдля сборки и запуска Swift-приложения в контейнере.