Swift Package Manager для Swift: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Swift Package Manager (SPM): Полное руководство для разработчиков на Swift

Swift Package Manager (SPM) — это официальный инструмент для управления зависимостями, сборки и распространения кода на языке Swift. Встроенный непосредственно в экосистему Swift, SPM предоставляет единый и стандартизированный способ работы с пакетами, избавляя разработчиков от необходимости использовать сторонние решения, такие как CocoaPods или Carthage. SPM тесно интегрирован с компилятором Swift и Xcode, что делает его идеальным выбором для современных проектов.

Зачем нужен Swift Package Manager? В первую очередь, для управления зависимостями. Любой нетривиальный проект использует сторонние библиотеки: для работы с сетью (Alamofire, URLSession), для парсинга JSON (SwiftyJSON), для работы с базами данных (GRDB, SQLite.swift) и т.д. SPM автоматически загружает, собирает и связывает эти библиотеки с вашим проектом. Во-вторых, SPM позволяет вам самим создавать и публиковать собственные пакеты, делая ваш код многократно используемым. В-третьих, он предоставляет мощный инструмент для сборки проектов, включая поддержку тестов, ресурсов и плагинов.

В отличие от CocoaPods, SPM не требует установки отдельного Ruby-гема или настройки сложного Podfile. В отличие от Carthage, SPM не требует ручного связывания фреймворков. SPM — это "родной" менеджер пакетов, который понимает структуру Swift-проектов "из коробки". Он использует файл Package.swift для описания проекта и его зависимостей, что делает процесс конфигурации прозрачным и предсказуемым.

Установка

Swift Package Manager поставляется вместе с официальным дистрибутивом Swift. Отдельная установка не требуется. Если у вас установлен Swift (начиная с версии 5.0), то SPM уже доступен в вашей системе.

Проверка установки:

swift --version

Если вы видите версию Swift, то SPM готов к работе. Вы также можете проверить наличие самого инструмента:

swift package --version

Установка Swift (если не установлен):

  • macOS: Установите Xcode из Mac App Store. Swift и SPM входят в его состав.
  • Linux (Ubuntu, CentOS, Amazon Linux): Скачайте и установите дистрибутив с официального сайта swift.org.
  • Windows: Скачайте и установите дистрибутив с swift.org.

Быстрый старт

Создадим новый исполняемый проект с помощью SPM. Это минимальный рабочий пример.

1. Создание проекта:

mkdir MySwiftProject
cd MySwiftProject
swift package init --type executable

Эта команда создаст структуру проекта:

  • Package.swift — файл манифеста.
  • Sources/MySwiftProject/main.swift — точка входа.
  • Tests/ — директория для тестов.

2. Содержимое Package.swift:

// swift-tools-version:5.9
import PackageDescription

let package = Package(
    name: "MySwiftProject",
    targets: [
        .executableTarget(
            name: "MySwiftProject"
        )
    ]
)

3. Содержимое main.swift:

print("Hello, Swift Package Manager!")

4. Сборка и запуск:

swift build
swift run

Вы должны увидеть в консоли: Hello, Swift Package Manager!

Основные методы, функции и классы

Рассмотрим ключевые элементы SPM, с которыми вы будете сталкиваться ежедневно. В основном это API, предоставляемый модулем PackageDescription.

1. Package

Сигнатура: Package(name: String, platforms: [SupportedPlatform]? = nil, products: [Product]? = nil, dependencies: [Package.Dependency]? = nil, targets: [Target]? = nil, swiftLanguageVersions: [SwiftVersion]? = nil, cLanguageStandard: CLanguageStandard? = nil, cxxLanguageStandard: CXXLanguageStandard? = nil)

Что делает: Это главный класс, описывающий весь пакет. Он определяет имя, платформы, продукты (библиотеки/исполняемые файлы), зависимости и цели сборки.

Пример: (см. пример в "Быстрый старт")

2. Target

Сигнатура: Target.target(name: String, dependencies: [Target.Dependency]? = nil, path: String? = nil, exclude: [String]? = nil, sources: [String]? = nil, resources: [Resource]? = nil, publicHeadersPath: String? = nil, cSettings: [CSetting]? = nil, cxxSettings: [CXXSetting]? = nil, swiftSettings: [SwiftSetting]? = nil, linkerSettings: [LinkerSetting]? = nil, plugins: [Target.PluginUsage]? = nil)

Что делает: Определяет цель сборки (модуль). Цель может быть исполняемой (.executableTarget) или библиотекой (.target). Указывает зависимости от других целей и пакетов.

Пример:

targets: [
    .target(
        name: "MyLibrary",
        dependencies: ["SomeDependency"]
    ),
    .executableTarget(
        name: "MyApp",
        dependencies: ["MyLibrary"]
    )
]

3. Product

Сигнатура: Product.library(name: String, type: Product.Library.LibraryType? = nil, targets: [String]) или Product.executable(name: String, targets: [String])

Что делает: Определяет, что "экспортирует" ваш пакет для внешнего мира. Это может быть библиотека (статическая или динамическая) или исполняемый файл.

Пример:

products: [
    .library(
        name: "MyLibrary",
        targets: ["MyLibrary"]
    ),
    .executable(
        name: "MyTool",
        targets: ["MyTool"]
    )
]

4. Package.Dependency

Сигнатура: .package(url: String, from: Version), .package(url: String, exact: Version), .package(url: String, branch: String), .package(url: String, revision: String), .package(path: String) и другие.

Что делает: Описывает зависимость от другого пакета. Указывает источник (URL репозитория, локальный путь) и требования к версии.

Пример:

dependencies: [
    .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
    .package(path: "../MyLocalPackage")
]

5. Version

Сигнатура: Version(_ string: String) или Version(major: Int, minor: Int, patch: Int)

Что делает: Представляет семантическую версию (SemVer). Используется для указания версий зависимостей.

Пример:

let minVersion: Version = "1.2.3"
let exactVersion = Version(2, 0, 0)

6. SupportedPlatform

Сигнатура: .macOS(_ version: SupportedPlatform.MacOSVersion), .iOS(_ version: SupportedPlatform.iOSVersion), .tvOS(_ version: SupportedPlatform.TVOSVersion), .watchOS(_ version: SupportedPlatform.WatchOSVersion), .visionOS(_ version: SupportedPlatform.VisionOSVersion), .linux(), .macCatalyst(_ version: SupportedPlatform.MacCatalystVersion), .driverKit(_ version: SupportedPlatform.DriverKitVersion)

Что делает: Устанавливает минимальные версии платформ, которые поддерживает ваш пакет.

Пример:

platforms: [
    .iOS(.v16),
    .macOS(.v13)
]

7. Resource

Сигнатура: .process(_ path: String), .copy(_ path: String)

Что делает: Указывает, как обрабатывать ресурсы (изображения, JSON-файлы, шрифты) в цели. .process применяет стандартные правила (например, для XIB), .copy просто копирует файл.

Пример:

resources: [
    .process("Resources"),
    .copy("config.json")
]

8. Plugin

Сигнатура: .plugin(name: String, package: String? = nil)

Что делает: Определяет зависимость от плагина (например, для генерации кода или линтинга). Плагины — мощная функция SPM, появившаяся в Swift 5.6.

Пример:

targets: [
    .target(
        name: "MyTarget",
        plugins: [
            .plugin(name: "SwiftLintPlugin", package: "SwiftLint")
        ]
    )
]

9. SwiftSetting

Сигнатура: .define(_ name: String, _ value: String? = nil), .unsafeFlags([String]), .enableUpcomingFeature(_ feature: SwiftSetting.UpcomingFeature), .enableExperimentalFeature(_ feature: SwiftSetting.ExperimentalFeature)

Что делает: Позволяет передавать флаги компилятору Swift для конкретной цели. Например, для определения макросов препроцессора или включения экспериментальных возможностей.

Пример:

swiftSettings: [
    .define("DEBUG"),
    .enableUpcomingFeature("BareSlashRegexLiterals")
]

Реальный практический пример (100-200 строк кода)

Создадим пакет для работы с погодой, который будет получать данные из открытого API (Open-Meteo) и выводить их в консоль. Это покажет, как использовать зависимости, асинхронность и структуру SPM.

1. Создаем проект:

mkdir WeatherKit
cd WeatherKit
swift package init --type executable

2. Редактируем Package.swift:

// swift-tools-version:5.9
import PackageDescription

let package = Package(
    name: "WeatherKit",
    platforms: [
        .macOS(.v10_15) // Для async/await
    ],
    dependencies: [
        // Зависимость для работы с сетью (не обязательно, но упростит код)
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0")
    ],
    targets: [
        .executableTarget(
            name: "WeatherKit",
            dependencies: ["Alamofire"]
        )
    ]
)

3. Редактируем Sources/WeatherKit/main.swift:

import Foundation
import Alamofire

// MARK: - Модели данных
struct WeatherResponse: Codable {
    let latitude: Double
    let longitude: Double
    let currentWeather: CurrentWeather

    enum CodingKeys: String, CodingKey {
        case latitude, longitude
        case currentWeather = "current_weather"
    }
}

struct CurrentWeather: Codable {
    let temperature: Double
    let windspeed: Double
    let weathercode: Int
    let time: String
}

// MARK: - Сервис погоды
class WeatherService {
    private let baseURL = "https://api.open-meteo.com/v1/forecast"

    /// Получает текущую погоду для заданных координат
    /// - Parameters:
    ///   - latitude: Широта
    ///   - longitude: Долгота
    /// - Returns: WeatherResponse
    func fetchWeather(latitude: Double, longitude: Double) async throws -> WeatherResponse {
        let parameters: [String: Any] = [
            "latitude": latitude,
            "longitude": longitude,
            "current_weather": true
        ]

        return try await withCheckedThrowingContinuation { continuation in
            AF.request(baseURL, parameters: parameters)
                .validate()
                .responseDecodable(of: WeatherResponse.self) { response in
                    switch response.result {
                    case .success(let weather):
                        continuation.resume(returning: weather)
                    case .failure(let error):
                        continuation.resume(throwing: error)
                    }
                }
        }
    }

    /// Описание погоды на основе кода
    static func weatherDescription(from code: Int) -> String {
        switch code {
        case 0: return "Ясно"
        case 1, 2, 3: return "Облачно"
        case 45, 48: return "Туман"
        case 51, 53, 55: return "Морось"
        case 61, 63, 65: return "Дождь"
        case 71, 73, 75: return "Снег"
        case 95: return "Гроза"
        default: return "Неизвестно"
        }
    }
}

// MARK: - Точка входа
@main
struct WeatherApp {
    static func main() async {
        let service = WeatherService()

        print("🌤️ Погодный терминал")
        print("Введите широту (например, 55.7558): ", terminator: "")
        guard let latInput = readLine(), let latitude = Double(latInput) else {
            print("❌ Ошибка: неверная широта")
            return
        }

        print("Введите долготу (например, 37.6173): ", terminator: "")
        guard let lonInput = readLine(), let longitude = Double(lonInput) else {
            print("❌ Ошибка: неверная долгота                    

Рекомендаций