Перейти к содержимому

Практика 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

  1. Создайте Swift-пакет Greeting:
    • swift package init --name Greeting --type library;
    • в модуле реализуйте функцию greet(name: String) -> String, возвращающую "Привет, \(name)! Добро пожаловать в Swift.";
    • в файле Tests/GreetingTests/GreetingTests.swift напишите тест, проверяющий результат для нескольких имён;
    • запустите swift build и swift test, убедитесь, что всё проходит.

Пример ожидаемого поведения:

Sources/Greeting/Greeting.swift
public func greet(name: String) -> String {
"Привет, \(name)! Добро пожаловать в Swift."
}
// Tests/GreetingTests/GreetingTests.swift
import XCTest
@testable import Greeting
final class GreetingTests: XCTestCase {
func testGreet() {
XCTAssertEqual(greet(name: "Анна"), "Привет, Анна! Добро пожаловать в Swift.")
}
func testGreetEmpty() {
XCTAssertTrue(greet(name: "").contains("Привет"))
}
}
  1. Добавьте в пакет Greeting второй таргет — исполняемый GreetingCLI, который принимает имя из аргументов командной строки и вызывает greet(name:). Обновите Package.swift:
    • добавьте .executableTarget(name: "GreetingCLI", dependencies: ["Greeting"]);
    • запустите через swift run GreetingCLI Мир.

Пример структуры Package.swift:

5.9
import PackageDescription
let package = Package(
name: "Greeting",
targets: [
.target(name: "Greeting"),
.executableTarget(name: "GreetingCLI", dependencies: ["Greeting"]),
.testTarget(name: "GreetingTests", dependencies: ["Greeting"]),
]
)

B. CLI-утилита с JSON

  1. Напишите 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"
]
}
  1. Расширьте jsonformat: добавьте флаг --compact, выводящий JSON в одну строку (без пробелов), и флаг --sort-keys для сортировки ключей. Реализуйте разбор аргументов вручную (без внешних зависимостей) или с помощью swift-argument-parser.

C. Codable и работа с данными

  1. Создайте 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"]
}
  1. Создайте 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

  1. Перепишите следующий Python-скрипт на Swift. Сравните читаемость, типобезопасность и обработку ошибок:
import json
import sys
from 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-утилита

  1. Создайте SPM-пакет TaskManager — CLI-приложение для управления задачами:
    • Хранение данных в JSON-файле (tasks.json);
    • Команды (через аргументы командной строки):
      • add "Название задачи" — добавить задачу;
      • list — показать все задачи;
      • done <id> — отметить задачу выполненной;
      • remove <id> — удалить задачу;
    • Модель данных:
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-15

F. Мини-проект — серверная разработка (Vapor)

  1. Создайте минимальный Vapor-сервер:
    • swift package init --name MiniServer --type executable;
    • добавьте зависимость Vapor в Package.swift:
5.9
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!
  1. Расширьте сервер из задания 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-приложения в контейнере.