Какие преимущества и недостатки у Swift Package Manager (SPM) как менеджера зависимостей?

«Какие преимущества и недостатки у Swift Package Manager (SPM) как менеджера зависимостей?» — вопрос из категории DevOps, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Преимущества:

  • Нативная интеграция: Встроен прямо в Xcode и Swift, не требует установки дополнительных инструментов.
  • Декларативная конфигурация: Все зависимости и цели проекта описываются в одном файле Package.swift на языке Swift.
  • Поддержка семантического версионирования: Позволяет точно указывать диапазоны версий (.upToNextMajor, .exact).
  • Управление зависимостями на уровне продукта: Пакет может объявлять несколько библиотечных или исполняемых продуктов.
  • Кроссплатформенность и поддержка серверного Swift: Работает на всех платформах, где есть Swift.

Недостатки и ограничения:

  • Ограниченная поддержка бинарных зависимостей: Хотя бинарные цели (binaryTarget) существуют, их использование менее удобно, чем в CocoaPods (например, для закрытых бинарных SDK).
  • Отсутствие центрального репозитория: Нет аналога CocoaPods Trunk, что усложняет поиск пакетов (хотя интеграция с GitHub и другими Git-хостами хорошая).
  • Сложности с ресурсами для iOS/macOS: Поддержка ресурсов (изображений, xib, storyboard) появилась позже и может требовать дополнительной настройки.
  • Нет встроенного кэширования зависимостей: При отсутствии локального клона пакет загружается заново, в отличие от CocoaPods, который кэширует исходники в ~/.cocoapods.

Пример файла Package.swift:

// swift-tools-version:5.9
import PackageDescription

let package = Package(
    name: "MyLibrary",
    platforms: [
        .iOS(.v15),
        .macOS(.v12)
    ],
    products: [
        .library(
            name: "MyLibrary",
            targets: ["MyLibrary"]),
        .executable(
            name: "MyTool",
            targets: ["MyTool"]),
    ],
    dependencies: [
        // Зависимость из репозитория Git с диапазоном версий
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
        // Зависимость из конкретной ветки
        .package(url: "https://github.com/ReactiveX/RxSwift.git", branch: "main"),
        // Зависимость из локального пути (для разработки)
        .package(path: "../MyLocalPackage"),
        // Зависимость с точной версией
        .package(url: "https://github.com/SnapKit/SnapKit.git", exact: "5.6.0")
    ],
    targets: [
        .target(
            name: "MyLibrary",
            dependencies: [
                .product(name: "Alamofire", package: "Alamofire"),
                "RxSwift", // Имя пакета может использоваться как имя зависимости, если оно совпадает
                .product(name: "RxCocoa", package: "RxSwift")
            ],
            resources: [.process("Resources")] // Включение ресурсов
        ),
        .executableTarget(
            name: "MyTool",
            dependencies: ["MyLibrary"]
        ),
        .testTarget(
            name: "MyLibraryTests",
            dependencies: ["MyLibrary", "SnapKit"] // Зависимость только для тестов
        ),
        // Бинарная цель (XCFramework)
        .binaryTarget(
            name: "MyBinarySDK",
            url: "https://example.com/MyBinarySDK.xcframework.zip",
            checksum: "abc123..."
        )
    ]
)