Форум программистов, компьютерный форум, киберфорум
mobDevWorks
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Swift 6.1 - улучшения параллелизма, Package Traits и многое другое. Часть 2

Запись от mobDevWorks размещена 09.08.2025 в 16:56
Показов 2973 Комментарии 0

Нажмите на изображение для увеличения
Название: Swift 6.1 - улучшения параллелизма, Package Traits 2.jpg
Просмотров: 225
Размер:	175.2 Кб
ID:	11044
Первая часть.

Управление зависимостями всегда было тем еще квестом. За свою карьеру я перепробовал множество подходов, от ручного добавления исходников до CocoaPods, Carthage и, наконец, Swift Package Manager. И должен признаться, что с каждым инструментом возникали свои специфические проблемы. Особенно когда речь заходила о кросс-платформенной разработке или условной компиляции разных наборов фич в зависимости от окружения.

Package Traits - новый подход к управлению зависимостями



Swift Package Manager (SPM) значительно упростил управление зависимостями, но до недавнего времени ему не хватало гибкости при работе с пакетами, которые должны вести себя по-разному в разных контекстах. Приходилось городить огороды из директив условной компиляции:

Swift
1
2
3
4
5
6
7
#if os(iOS)
// Код для iOS
#elseif os(macOS)
// Код для macOS
#elseif os(Linux)
// Код для Linux
#endif
И вот в Swift 6.1 появилась, на мой взгляд, революционная фича — Package Traits (трейты пакетов). Эта концепция кардинально меняет подход к управлению зависимостями и условной компиляции.

Что же такое Package Traits? По сути, это механизм, позволяющий определять набор характеристик, которые предоставляет пакет. Эти характеристики могут использоваться для условной компиляции и определения опциональных зависимостей. Проще говоря, вы можете легко указать, какие API и функции доступны в разных средах использования.

Давайте посмотрим, как это работает на практике. Вот как выглядит определение трейтов в файле Package.swift:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Package.swift
let package = Package(
    name: "MyNetworkLibrary",
    traits: ["Secure", "Logging", "UISupport"],
    platforms: [.iOS(.v15), .macOS(.v12)],
    products: [
        .library(
            name: "MyNetworkLibrary",
            targets: ["MyNetworkLibrary"]
        )
    ],
    dependencies: [
        .package(url: "https://github.com/example/crypto.git", from: "1.0.0", traits: ["Secure"]),
        .package(url: "https://github.com/example/logger.git", from: "2.0.0", traits: ["Logging"])
    ],
    targets: [
        .target(
            name: "MyNetworkLibrary",
            dependencies: [
                .product(name: "Crypto", package: "crypto", traits: ["Secure"]),
                .product(name: "Logger", package: "logger", traits: ["Logging"])
            ]
        )
    ]
)
В этом примере я определил три трейта для своей библиотеки: "Secure", "Logging" и "UISupport". Криптографические возможности подключаются только если активирован трейт "Secure", а логирование — при наличии трейта "Logging".
А вот как можно использовать трейты в самом коде:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@available(trait: Secure)
public func encryptData(_ data: Data) -> Data {
    // Реализация шифрования, доступная только при активном трейте Secure
    return Crypto.encrypt(data)
}
 
@available(trait: Logging)
public func logRequest(_ request: URLRequest) {
    // Логирование, доступное только при активном трейте Logging
    Logger.log(request)
}
 
@available(trait: UISupport)
public class NetworkIndicator {
    // UI-компоненты, доступные только при активном трейте UISupport
}
Когда другой разработчик будет использовать мою библиотеку, он сможет выбрать только те трейты, которые ему нужны:

Swift
1
2
3
4
5
6
7
8
// В проекте, использующем мою библиотеку
dependencies: [
    .package(
        url: "https://github.com/me/mynetworklibrary.git", 
        from: "1.0.0", 
        traits: ["Secure", "Logging"] // UISupport не используется
    )
]
Это дает огромные преимущества. Во-первых, код становится чище — вместо множества вложенных директив #if/#endif получаем аккуратные аннотации @available(trait. Во-вторых, снижается размер итогового бинарника, так как компилируется только нужный код. В-третьих, улучшается производительность компиляции, особенно в больших проектах. Но самое главное — Package Traits позволяют создавать по-настоящему модульные библиотеки, компоненты которых можно подключать по отдельности в зависимости от потребностей. Это особенно ценно для кросс-платформенной разработки.

Недавно я разрабатывал библиотеку для обработки данных, которая должна была работать и на iOS, и на Linux-серверах. Раньше мне приходилось поддерживать практически два параллельных кодбейса с кучей условной компиляции. С Package Traits структура упростилась:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Общий код без трейтов
public struct DataProcessor {
    public func process(_ data: Data) -> ProcessedData {
        // Базовая обработка, доступная везде
    }
}
 
// iOS-специфичный код
@available(trait: iOS)
extension DataProcessor {
    public func visualize() -> UIImage {
        // Визуализация для iOS
    }
}
 
// Server-специфичный код
@available(trait: Server)
extension DataProcessor {
    public func benchmark() -> PerformanceMetrics {
        // Измерение производительности для серверов
    }
}
А потом в Package.swift я просто определил соответствующие трейты:

Swift
1
2
3
4
5
let package = Package(
    name: "DataTools",
    traits: ["iOS", "Server"],
    // ...остальная конфигурация...
)
Теперь клиенты на iOS подключают пакет с трейтом "iOS", а серверы — с трейтом "Server". Код стал не только чище, но и безопаснее — компилятор не даст использовать функции, недоступные в текущем контексте.

Package Traits также решают давнюю проблему "раздутых" зависимостей. Часто библиотеки тянут за собой кучу дополнительных пакетов, которые не всегда нужны. Теперь с помощью трейтов можно сделать большинство зависимостей опциональными.

Для меня это настоящий game-changer. Раньше приходилось выбирать между созданием множества маленьких специализированных пакетов или одного большого универсального. Теперь можно иметь лучшее из обоих подходов — единый пакет с чётко разделенными компонентами. И что особенно важно, Package Traits работают не только для Swift-кода, но и для нативных библиотек. Например, можно включать разные бинарные зависимости для разных архитектур или операционных систем, что раньше требовало сложных скриптов сборки.

Конечно, как и любая новая технология, Package Traits требуют некоторого переосмысления подходов к организации кода. Но поверьте моему опыту — это та инвестиция времени, которая окупается очень быстро, особенно в долгосрочных проектах.

Визуальная часть в Xcode with Swift
подскажите идеи реализации такого таб бара в SWIFT: 1 - что бы были такие вкладки 2 - что бы...

Новый язык программирования swift и новый ios sdk
Вообщем кто что думает, на сколько сильно этот новый язык отличен от objetive c и перестанет ли...

Документация SWIFT
Здравствуйте. Не могли бы вы в эту тему накидать документации, особенностей и полезной инфы про...

Как установить swift на windows 8?
Всем привет, подскажите пожалуйста, как установить swift. ОС виндовс 8. Очень нужно )


Механизм наследования trait'ов и их композиция



Разбираясь с Package Traits глубже, я обнаружил одну из самых мощных их особенностей — механизм наследования и композиции. Именно эта возможность превращает трейты из простых флагов в полноценную систему организации и управления функциональностью пакетов. Попробую объяснить, как это работает и почему это так круто.

Наследование трейтов позволяет одним пакетам "наследовать" трейты от своих зависимостей. Допустим, у нас есть базовый пакет с криптографическими алгоритмами:

Swift
1
2
3
4
5
6
// В CryptoCore/Package.swift
let package = Package(
  name: "CryptoCore",
  traits: ["AES", "RSA", "ECC"],
  // ...
)
Теперь мы создаем другой пакет, который использует этот базовый:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
// В SecureMessaging/Package.swift
let package = Package(
  name: "SecureMessaging",
  traits: ["E2EEncryption"],
  dependencies: [
      .package(
          url: "path/to/CryptoCore", 
          from: "1.0.0",
          traits: ["AES", "ECC"]  // Используем только часть трейтов
      )
  ],
  // ...
)
В этом примере пакет SecureMessaging автоматически "получает" трейты AES и ECC от CryptoCore. Если какой-то код в SecureMessaging помечен как @available(trait: AES), он будет скомпилирован только если трейт AES активирован в CryptoCore. Это создает элегантную цепочку зависимостей между функциональностями разных пакетов.

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

Swift
1
2
3
4
5
6
7
8
9
10
11
12
// В файле Package.swift
let package = Package(
  name: "NetworkingSuite",
  traits: [
      "HTTP", 
      "WebSockets", 
      "Security", 
      "AdvancedHTTPS": ["HTTP", "Security"], // Составной трейт
      "SecureRealtime": ["WebSockets", "Security"] // Еще один составной трейт
  ],
  // ...
)
Здесь мы определяем два составных трейта: "AdvancedHTTPS", который активируется только при наличии трейтов "HTTP" и "Security", и "SecureRealtime", требующий "WebSockets" и "Security". Это позволяет создавать более сложные зависимости между компонентами. В коде мы можем использовать их так:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@available(trait: HTTP)
func performHTTPRequest() {
  // Базовый HTTP-запрос
}
 
@available(trait: Security)
func encryptData(_ data: Data) -> Data {
  // Шифрование данных
}
 
@available(trait: AdvancedHTTPS)
func performSecureRequest() {
  let data = prepareRequestData()
  let encrypted = encryptData(data) // Доступно, т.к. трейт Security активирован
  performHTTPRequest() // Доступно, т.к. трейт HTTP активирован
  // Дополнительная логика для HTTPS
}
В моей практике я столкнулся с интересным случаем, когда нам требовалось гибко настраивать функциональность библиотеки для работы с финансовыми данными. У нас были разные требования к безопасности и производительности в зависимости от типа клиента. Используя композицию трейтов, мы создали несколько профилей:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
// FinTech/Package.swift
let package = Package(
  name: "FinTech",
  traits: [
      "BasicSecurity",
      "AdvancedSecurity",
      "HighPerformance",
      "EnterpriseSecurity": ["AdvancedSecurity", "AuditLogs", "ComplianceReports"],
      "ConsumerFinance": ["BasicSecurity", "HighPerformance", "UserFriendly"],
      "InstitutionalFinance": ["EnterpriseSecurity", "HighPerformance"]
  ],
  // ...
)
Такой подход позволил нам адаптировать одну библиотеку для разных типов клиентов, не поддерживая несколько параллельных веток кода.

Что касается разрешения конфликтов, Swift 6.1 предлагает гибкий механизм. Когда несколько зависимостей требуют разных версий одного и того же трейта, пакет-потребитель может явно указать, какая версия должна использоваться:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
dependencies: [
  .package(
      url: "packageA", 
      from: "1.0.0",
      traits: ["Logging": "v1"]
  ),
  .package(
      url: "packageB", 
      from: "2.0.0",
      traits: ["Logging": "v2", override: true]
  )
]
Флаг override: true указывает, что версия трейта из packageB должна иметь приоритет над версией из packageA. Это особенно полезно при интеграции сторонних библиотек с разными требованиями.

При проектировании иерархии трейтов я обнаружил полезный паттерн — создание "базовых" и "расширеных" наборов функциональности:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
// DataProcessing/Package.swift
let package = Package(
  name: "DataProcessing",
  traits: [
      "Core", // Базовая функциональность, всегда включена
      "Basic": ["Core"], // Минимальный набор для простых случаев
      "Standard": ["Basic", "Filtering", "Sorting"], // Стандартный набор
      "Advanced": ["Standard", "ML", "StatisticalAnalysis"], // Продвинутый набор
      "Enterprise": ["Advanced", "DistributedProcessing", "HighSecurity"] // Полный набор
  ],
  // ...
)
Пользователи могут выбрать уровень функциональности, который им необходим, просто указав соответствующий составной трейт:

Swift
1
2
3
4
5
6
7
dependencies: [
  .package(
      url: "path/to/DataProcessing", 
      from: "1.0.0",
      traits: ["Standard"] // Включает Core, Basic, Filtering и Sorting
  )
]
Что особенно круто в наследовании трейтов — это возможность создавать сложные зависимости между компонентами без жесткой связанности кода. Вместо того чтобы писать условную логику вроде "если доступен компонент A и компонент B, то выполнить X", мы просто помечаем код соответствующим составным трейтом и позволяем системе сборки решать, должен ли он быть включен.

Механизм наследования трейтов также хорошо работает с версионированием. При обновлении пакета можно вводить новые трейты или модифицировать существующие, не нарушая обратную совместимость:

Swift
1
2
3
4
5
6
7
8
9
10
// В версии 1.0.0
traits: ["Basic", "Advanced"]
 
// В версии 2.0.0
traits: [
  "Basic", 
  "Advanced", 
  "Experimental", // Новый трейт
  "LegacyAdvanced": ["Advanced"] // Для обратной совместимости
]
Клиенты, использующие "Advanced" в версии 1.0.0, могут безболезненно перейти на "LegacyAdvanced" в версии 2.0.0, если новая реализация "Advanced" им не подходит.

В заключение этого раздела хочу отметить, что наследование и композиция трейтов — это те инструменты, которые превращают Package Traits из простой замены условной компиляции в мощный механизм организации модульного кода. С их помощью можно создавать гибкие, масштабируемые архитектуры, которые легко адаптируются под разные требования без дублирования кода и сложных условных конструкций.

Конфигурация и использование в реальных проектах



Теория теорией, но когда дело доходит до реальных проектов, Package Traits требуют определенного подхода к конфигурации и использованию. За последние месяцы я внедрил эту технологию в несколько действующих проектов и хочу поделиться практическими наблюдениями и советами.

Начнем с базовой настройки. Для существующего проекта первый шаг — это модификация файла Package.swift для определения трейтов. Вот пример конфигурации для типичного бизнес-приложения:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Package.swift
let package = Package(
    name: "BusinessApp",
    traits: [
        "Core",                               // Базовая функциональность
        "UI",                                 // Пользовательский интерфейс
        "Networking",                         // Сетевой слой
        "Analytics",                          // Аналитика
        "Reporting",                          // Отчеты
        "AdvancedReporting": ["Reporting", "Analytics"], // Продвинутые отчеты
        "FullPack": ["Core", "UI", "Networking", "Analytics", "Reporting"] // Полный набор
    ],
    platforms: [.iOS(.v15), .macOS(.v13)],
    products: [
        .library(
            name: "BusinessCore",
            targets: ["BusinessCore"],
            traits: ["Core"] // Только базовая функциональность
        ),
        .library(
            name: "BusinessComplete",
            targets: ["BusinessComplete"],
            traits: ["FullPack"] // Полный функционал
        )
    ],
    dependencies: [
        .package(url: "https://example.com/networking.git", from: "1.0.0", traits: ["Networking"]),
        .package(url: "https://example.com/analytics.git", from: "2.0.0", traits: ["Analytics"])
    ],
    targets: [
        .target(
            name: "BusinessCore",
            dependencies: []
        ),
        .target(
            name: "BusinessComplete",
            dependencies: [
                "BusinessCore",
                .product(name: "Networking", package: "networking", traits: ["Networking"]),
                .product(name: "Analytics", package: "analytics", traits: ["Analytics"])
            ]
        ),
        .testTarget(
            name: "BusinessTests",
            dependencies: ["BusinessComplete"]
        )
    ]
)
В этой конфигурации я определил несколько базовых трейтов и два составных — "AdvancedReporting" и "FullPack". Обратите внимание, что я создал два разных продукта: облегченный "BusinessCore" только с базовой функциональностью и полнофункциональный "BusinessComplete".

После настройки Package.swift следующий шаг — маркировка кода с помощью аннотаций @available(trait:). Вот как это выглядит на практике:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Базовая функциональность, доступна всегда
public struct User {
    public let id: UUID
    public let name: String
}
 
// Функции для работы с сетью, доступны только при активном трейте Networking
@available(trait: Networking)
public extension User {
    func sync() async throws {
        // Синхронизация с сервером
    }
    
    static func fetch(id: UUID) async throws -> User {
        // Загрузка пользователя с сервера
    }
}
 
// Аналитика, доступна только при активном трейте Analytics
@available(trait: Analytics)
public extension User {
    func trackActivity(_ activity: UserActivity) {
        // Отправка данных в аналитику
    }
}
 
// Функционал отчетов, требует трейта Reporting
@available(trait: Reporting)
public func generateUserReport(for user: User) -> Report {
    // Генерация базового отчета
    return Report(user: user)
}
 
// Продвинутая отчетность, требует составного трейта AdvancedReporting
@available(trait: AdvancedReporting)
public func generateDetailedUserReport(for user: User) -> DetailedReport {
    // Используем базовый отчет
    let baseReport = generateUserReport(for: user)
    // Добавляем аналитические данные
    user.trackActivity(.reportGenerated)
    // Создаем расширенный отчет
    return DetailedReport(baseReport: baseReport, analytics: fetchAnalytics(for: user))
}
Важное практическое замечание: маркируйте трейтами не только публичный API, но и внутренние детали реализации. Это позволит компилятору исключить ненужный код и избежать ошибок, связаных с отсутствием зависимостей.

Конфигурация и использование в реальных проектах



Начнем с базовой конфигурации в файле Package.swift. Самый простой способ определить трейты выглядит так:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
// Package.swift
let package = Package(
    name: "AnalyticsService",
    traits: ["Basic", "Premium", "Enterprise"],
    platforms: [.iOS(.v15), .macOS(.v12)],
    products: [
        .library(
            name: "AnalyticsService",
            targets: ["AnalyticsService"]
        )
    ],
    // ...
)
Но в реальной жизни всё обычно сложнее. Мне недавно пришлось переработать большую библиотеку аналитики, которая имела разные возможности для разных клиентов. Раньше у нас было три отдельные версии, которые приходилось синхронизировать вручную. С Package Traits решение выглядит гораздо элегантнее:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Package.swift
let package = Package(
    name: "AnalyticsService",
    traits: [
        "Core", // Базовый функционал
        "SessionTracking", // Отслеживание сессий
        "Heatmaps", // Тепловые карты
        "NetworkMonitoring", // Мониторинг сети
        "CrashReporting", // Отчеты о крешах
        "UserJourneyAnalytics", // Аналитика пользовательского пути
        
        // Составные трейты для разных уровней сервиса
        "Basic": ["Core", "SessionTracking"],
        "Standard": ["Basic", "Heatmaps", "CrashReporting"],
        "Premium": ["Standard", "NetworkMonitoring", "UserJourneyAnalytics"]
    ],
    platforms: [.iOS(.v15), .macOS(.v12)],
    products: [
        .library(
            name: "AnalyticsService",
            targets: ["AnalyticsService"]
        )
    ],
    dependencies: [
        .package(
            url: "https://github.com/example/networking.git", 
            from: "1.0.0",
            traits: ["Secure", "Compression"] // Необходимые трейты зависимости
        ),
        .package(
            url: "https://github.com/example/storage.git", 
            from: "2.0.0",
            traits: ["SQLite"] // Только SQLite хранилище, без других опций
        )
    ],
    targets: [
        .target(
            name: "AnalyticsService",
            dependencies: [
                .product(name: "Networking", package: "networking"),
                .product(name: "Storage", package: "storage")
            ]
        ),
        .testTarget(
            name: "AnalyticsServiceTests",
            dependencies: ["AnalyticsService"]
        )
    ]
)
В самом коде библиотеки я использую трейты для разделения функциональности:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Core functionality - всегда доступно
public class AnalyticsManager {
    public init() {
        // Инициализация
    }
    
    public func trackEvent(_ name: String, parameters: [String: Any]? = nil) {
        // Базовое отслеживание событий
    }
}
 
// Session tracking - доступно при активации трейта
@available(trait: SessionTracking)
extension AnalyticsManager {
    public func startSession() {
        // Начало сессии
    }
    
    public func endSession() {
        // Завершение сессии
    }
}
 
// Heatmaps - доступно при активации трейта
@available(trait: Heatmaps)
extension AnalyticsManager {
    public func enableHeatmapCollection() {
        // Включение сбора данных для тепловых карт
    }
    
    public func getHeatmapData() -> HeatmapData {
        // Получение данных тепловой карты
        return HeatmapData()
    }
}
 
// И так далее для других компонентов...
Теперь клиенты могут выбирать нужный уровень функциональности, просто указав соответствующий трейт:

Swift
1
2
3
4
5
6
7
8
// В проекте клиента
dependencies: [
    .package(
        url: "path/to/AnalyticsService", 
        from: "1.0.0",
        traits: ["Standard"] // Получаем Core, SessionTracking, Heatmaps и CrashReporting
    )
]
Интересный вопрос, который у меня возник при внедрении Package Traits — как эффективно тестировать код, зависящий от наличия определенных трейтов? Я разработал подход с использованием отдельных тестовых таргетов для разных комбинаций трейтов:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Package.swift (для тестирования)
targets: [
    // Основной тестовый таргет
    .testTarget(
        name: "CoreTests",
        dependencies: ["AnalyticsService"]
    ),
    // Тесты для Standard функциональности
    .testTarget(
        name: "StandardFeaturesTests",
        dependencies: ["AnalyticsService"],
        traits: ["Standard"]
    ),
    // Тесты для Premium функциональности
    .testTarget(
        name: "PremiumFeaturesTests",
        dependencies: ["AnalyticsService"],
        traits: ["Premium"]
    )
]
Это позволяет изолировать тесты и гарантировать, что функциональность корректно работает при разных конфигурациях трейтов.
Еще один практический аспект — обратная совместимость при обновлении библиотеки. Когда мы выпускаем новую версию с измененими трейтами, важно обеспечить плавный переход для существующих клиентов. Я выработал следующий подход:

Swift
1
2
3
4
5
6
7
8
9
// В версии 1.0.0
traits: ["Basic", "Advanced"]
 
// В версии 2.0.0
traits: [
    "Basic", 
    "Enhanced", // Новая версия Advanced с улучшениями
    "Legacy.Advanced": ["Enhanced"] // Алиас для обратной совместимости
]
Таким образом, клиенты, использующие трейт "Advanced", могут продолжать использовать его, но фактически получат новую функциональность "Enhanced".
При разработке внутренних библиотек в нашей компании я столкнулся с проблемой: как организовать совместное использование трейтов между разными пакетами, чтобы избежать дублирования определений? Решение оказалось не очевидным, но эффективным — создание "метапакета" с общими определениями трейтов:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// CommonTraits/Package.swift
let package = Package(
    name: "CommonTraits",
    traits: [
        "UI.Basic", "UI.Advanced",
        "Networking.Basic", "Networking.Secure",
        "Storage.SQLite", "Storage.CoreData"
    ],
    products: [
        .library(name: "CommonTraits", targets: ["CommonTraits"])
    ],
    targets: [
        .target(name: "CommonTraits", sources: ["empty.swift"])
    ]
)
Файл empty.swift содержит минимальный код, нужный только для корректной компиляции:

Swift
1
2
// empty.swift
public enum CommonTraits {}
Теперь другие пакеты могут импортировать эти трейты:

Swift
1
2
3
4
5
6
7
8
// NetworkingPackage/Package.swift
dependencies: [
    .package(url: "path/to/CommonTraits", from: "1.0.0")
],
traits: [
    "Basic": ["UI.Basic"], // Используем трейт из CommonTraits
    "Secure": ["Networking.Secure"] // Используем еще один трейт
]
Это обеспечивает единообразие трейтов во всей экосистеме пакетов и упрощает поддержку.

Использование трейтов в CI/CD пайплайнах



Отдельная история — интеграция Package Traits с системами непрерывной интеграции. В наших проектах мы активно используем GitHub Actions, и я разработал подход для автоматической проверки разных конфигураций трейтов:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# .github/workflows/test.yml
name: Run Tests
 
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
 
jobs:
  test-basic:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Test Basic Configuration
        run: swift test --traits Basic
 
  test-standard:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Test Standard Configuration
        run: swift test --traits Standard
 
  test-premium:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Test Premium Configuration
        run: swift test --traits Premium
Такой подход позволяет гарантировать, что все комбинации трейтов корректно работают после каждого изменения кода.

При разработке кроссплатформенных приложений Package Traits стали для меня настоящим спасением. Например, в одном проекте нам требовалось поддерживать iOS, macOS и даже Windows (через SwiftWasm). Раньше это была настоящая головная боль с множеством условных компиляций, а теперь структура упростилась:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Общий код
public struct DataProcessor {
    public func process(_ data: Data) -> ProcessedData {
        // Общая логика
    }
}
 
// iOS-специфичный код
@available(trait: iOS)
extension DataProcessor {
    public func visualizeOnDevice() {
        // iOS-реализация
    }
}
 
// macOS-специфичный код
@available(trait: macOS)
extension DataProcessor {
    public func visualizeOnDevice() {
        // macOS-реализация
    }
}
 
// Windows-специфичный код
@available(trait: Windows)
extension DataProcessor {
    public func visualizeOnDevice() {
        // Windows-реализация через WebAssembly
    }
}
Обратите внимание, что метод visualizeOnDevice() имеет разные реализации для разных платформ, но интерфейс остается одним и тем же. Это значительно упрощает код, использующий DataProcessor.

Интеграция с GitHub Actions и автоматизация CI/CD



После внедрения Package Traits в несколько проектов я быстро осознал, что их настоящая мощь раскрывается только при правильной интеграции с системами непрерывной интеграции. Особенно это касается проектов, где используются разные конфигурации трейтов для разных сред или клиентов.

Мой основной инструмент для CI/CD последние пару лет — GitHub Actions. И хотя я уже кратко упоминал интеграцию трейтов с системами CI в предыдущем разделе, хочу поделиться более детальным опытом построения действительно эффективных пайплайнов. Начнем с базовой матрицы для тестирования различных конфигураций трейтов:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# .github/workflows/build-and-test.yml
name: Build and Test
 
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]
 
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest]
        trait-set: [Basic, Standard, Premium]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Swift
        uses: swift-actions/setup-swift@v1
        with:
          swift-version: "6.1"
      
      - name: Build and Test
        run: swift test --traits ${{ matrix.trait-set }}
Этот простой пайплайн уже дает нам матрицу из 6 различных комбинаций (3 набора трейтов × 2 операционные системы). Но на реальных проектах все обычно сложнее.

В одном из моих недавних проектов нам требовалось не только тестировать разные наборы трейтов, но и собирать бинарные артефакты с разными конфигурациями. Решение выглядело примерно так:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# .github/workflows/release.yml
name: Create Release
 
on:
  push:
    tags:
      - 'v*'
 
jobs:
  build:
    name: Build and Package
    runs-on: macos-latest
    strategy:
      matrix:
        config: [
          {name: "Basic", traits: "Basic", suffix: "basic"},
          {name: "Standard", traits: "Standard", suffix: "standard"},
          {name: "Premium", traits: "Premium,Analytics", suffix: "premium"}
        ]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Swift
        uses: swift-actions/setup-swift@v1
        with:
          swift-version: "6.1"
      
      - name: Build ${{ matrix.config.name }} Package
        run: |
          swift build --traits ${{ matrix.config.traits }} -c release
          mv .build/release/MyApp .build/release/MyApp-${{ matrix.config.suffix }}
      
      - name: Upload Artifact
        uses: actions/upload-artifact@v3
        with:
          name: myapp-${{ matrix.config.suffix }}
          path: .build/release/MyApp-${{ matrix.config.suffix }}
  
  release:
    name: Create Release
    needs: build
    runs-on: ubuntu-latest
    
    steps:
      - name: Download Artifacts
        uses: actions/download-artifact@v3
        with:
          path: ./artifacts
      
      - name: Create Release
        uses: softprops/action-gh-release@v1
        with:
          files: |
            ./artifacts/myapp-basic/MyApp-basic
            ./artifacts/myapp-standard/MyApp-standard
            ./artifacts/myapp-premium/MyApp-premium
Такой подход позволяет автоматически создавать релизы с разными вариантами приложения в зависимости от включенных трейтов.

Но самое интересное начинается, когда вы интегрируете Package Traits с другими этапами CI/CD. Например, в одном проекте мы использовали трейты для условного запуска различных наборов тестов, включая интеграционные и UI-тесты:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# .github/workflows/comprehensive-testing.yml
name: Comprehensive Testing
 
on:
  schedule:
    - cron: '0 2 * * *'  # Запуск каждый день в 2:00 UTC
 
jobs:
  unit-tests:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Unit Tests
        run: swift test --traits Basic
 
  integration-tests:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Integration Tests
        run: swift test --traits "Standard,TestInfrastructure"
 
  ui-tests:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Simulator
        run: xcrun simctl create "iPhone 14" "iPhone 14" "iOS16.2"
      - name: Run UI Tests
        run: xcodebuild test -scheme MyApp -destination "platform=iOS Simulator,name=iPhone 14" -traits "UITesting"
В этом примере мы используем специальные трейты для тестирования, которые активируют различные наборы тестов. Это позволяет оптимизировать процесс CI/CD — например, быстрые юнит-тесты запускаются на каждом PR, а длительные UI-тесты — только ночью.

Еще один мощный сценарий — автоматизация сборки и публикации документации в зависимости от трейтов. Я внедрил его в один из наших опенсорсных проектов:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# .github/workflows/documentation.yml
name: Generate Documentation
 
on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  generate-docs:
    runs-on: macos-latest
    strategy:
      matrix:
        trait-set: [
          {traits: "Basic", folder: "basic"},
          {traits: "Standard", folder: "standard"},
          {traits: "Premium", folder: "premium"}
        ]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Swift
        uses: swift-actions/setup-swift@v1
      
      - name: Install swift-doc
        run: brew install swiftdocorg/tap/swift-doc
      
      - name: Generate Documentation
        run: |
          swift build --traits ${{ matrix.trait-set.traits }}
          swift-doc generate . --module-name MyLibrary --traits ${{ matrix.trait-set.traits }} --output docs/${{ matrix.trait-set.folder }}
      
      - name: Upload Documentation
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          folder: docs
          branch: gh-pages
Таким образом мы генерируем отдельные наборы документации для разных уровней API в зависимости от активированных трейтов. Пользователи могут видеть только те методы и классы, которые доступны в выбраной ими конфигурации.

Интересным вызовом для меня стала интеграция Package Traits с мониторингом производительности. Я разработал специальный воркфлоу для отслеживания изменений производительности при различных конфигурациях трейтов:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# .github/workflows/performance.yml
name: Performance Monitoring
 
on:
  push:
    branches: [ main ]
 
jobs:
  benchmark:
    runs-on: macos-latest
    strategy:
      matrix:
        trait-set: [Basic, Standard, Premium]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Run Benchmarks
        run: swift run --traits ${{ matrix.trait-set }} Benchmarks
      
      - name: Parse Results
        id: parse
        run: |
          RESULT=$(cat benchmark-results.json)
          echo "::set-output name=result::$RESULT"
      
      - name: Store Results
        uses: benchmark-action/github-action-benchmark@v1
        with:
          name: Swift Benchmark (${{ matrix.trait-set }})
          tool: 'customBiggerIsBetter'
          output-file-path: benchmark-results.json
          github-token: ${{ secrets.GITHUB_TOKEN }}
          auto-push: true
Этот пайплайн запускает бенчмарки для каждой конфигурации трейтов и сохраняет результаты в виде графиков на GitHub Pages. Это позволяет нам отслеживать, как различные комбинации трейтов влияют на производительность нашей библиотеки.

Но не все так гладко. В процессе внедрения этих пайплайнов я столкнулся с несколькими проблемами. Одна из них — ограничения GitHub Actions по времени выполнения. Если у вас много различных комбинаций трейтов, тестирование всех вариантов может занимать слишком много времени. Решение, которое я нашел — стратегическое разделение тестов на критические и некритические. Критические запускаются на каждом PR, а полная матрица комбинаций — только при мердже в основную ветку:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# .github/workflows/smart-testing.yml
name: Smart Testing
 
on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]
 
jobs:
  determine-tests:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - id: set-matrix
        run: |
          if [[ "${{ github.event_name }}" == "pull_request" ]]; then
            # Только базовые тесты для PR
            echo "matrix={"trait-set":["Basic"]}" >> $GITHUB_OUTPUT
          else
            # Полная матрица для мерджа в main
            echo "matrix={"trait-set":["Basic","Standard","Premium"]}" >> $GITHUB_OUTPUT
          fi
  
  test:
    needs: determine-tests
    runs-on: macos-latest
    strategy:
      matrix: ${{ fromJson(needs.determine-tests.outputs.matrix) }}
    
    steps:
      - uses: actions/checkout@v3
      - name: Run Tests
        run: swift test --traits ${{ matrix.trait-set }}
Еще одна особенность интеграции Package Traits с CI/CD — это управление зависимостями. Если у вас есть несколько пакетов с трейтами, которые зависят друг от друга, настройка правильного пайплайна может быть нетривиальной задачей. Я нашел элегантное решение с использованием монорепозитория и составных воркфлоу:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# .github/workflows/monorepo-ci.yml
name: Monorepo CI
 
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
 
jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      core: ${{ steps.filter.outputs.core }}
      network: ${{ steps.filter.outputs.network }}
      ui: ${{ steps.filter.outputs.ui }}
      app: ${{ steps.filter.outputs.app }}
    steps:
      - uses: actions/checkout@v3
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          filters: |
            core:
              - 'Packages/Core/[B]'
            network:
              - 'Packages/Network/[/B]'
            ui:
              - 'Packages/UI/[B]'
            app:
              - 'App/[/B]'
  
  test-core:
    needs: changes
    if: needs.changes.outputs.core == 'true'
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Test Core
        run: cd Packages/Core && swift test --traits "Base"
  
  test-network:
    needs: [changes, test-core]
    if: needs.changes.outputs.network == 'true' || needs.changes.outputs.core == 'true'
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Test Network
        run: cd Packages/Network && swift test --traits "Base,Secure"
  
  test-ui:
    needs: [changes, test-core]
    if: needs.changes.outputs.ui == 'true' || needs.changes.outputs.core == 'true'
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Test UI
        run: cd Packages/UI && swift test --traits "Base,Animations"
  
  test-app:
    needs: [changes, test-network, test-ui]
    if: always() && (needs.changes.outputs.app == 'true' || needs.changes.outputs.network == 'true' || needs.changes.outputs.ui == 'true' || needs.changes.outputs.core == 'true')
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Test App
        run: swift test --traits "Base,Secure,Animations"
Этот пайплайн анализирует, какие пакеты были изменены, и запускает тесты только для них и их зависимостей, что значительно ускоряет процесс CI/CD.

Автоматизация развертывания в разных средах



Отдельная тема — использование Package Traits для автоматизации развертывания приложений в разных средах (разработка, тестирование, продакшн). В одном из проектов я реализовал следующий подход:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# .github/workflows/deploy.yml
name: Deploy
 
on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        default: 'dev'
        type: choice
        options:
          - dev
          - test
          - prod
 
jobs:
  deploy:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup environment-specific traits
        id: traits
        run: |
          if [[ "${{ github.event.inputs.environment }}" == "dev" ]]; then
            echo "traits=Development,Logging,MockServices" >> $GITHUB_OUTPUT
          elif [[ "${{ github.event.inputs.environment }}" == "test" ]]; then
            echo "traits=TestEnvironment,Logging,Analytics" >> $GITHUB_OUTPUT
          else
            echo "traits=Production,Analytics" >> $GITHUB_OUTPUT
          fi
      
      - name: Build for ${{ github.event.inputs.environment }}
        run: swift build --traits ${{ steps.traits.outputs.traits }} -c release
      
      - name: Deploy to ${{ github.event.inputs.environment }}
        run: ./deploy.sh ${{ github.event.inputs.environment }}
Такой подход позволяет собирать разные варианты приложения для разных сред, активируя соответствующие трейты. Например, в dev-среде мы включаем подробное логирование и мок-сервисы, а в продакшене — только необходимую аналитику.

На одном из наших проектов мы пошли еще дальше и интегрировали Package Traits с Feature Flags. Это позволило нам иметь общую кодовую базу, но активировать разные функции для разных клиентов или сред:

Swift
1
2
3
4
5
6
7
8
9
10
11
12
// В коде приложения
struct FeatureManager {
  static func isEnabled(_ feature: Feature) -> Bool {
      #if trait(Premium)
      // В Premium-версии все функции доступны
      return true
      #else
      // В других версиях проверяем на сервере
      return checkFeatureFlagOnServer(feature)
      #endif
  }
}
Соответствующий CI/CD пайплайн выглядел примерно так:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# .github/workflows/client-specific-build.yml
name: Client-Specific Build
 
on:
  workflow_dispatch:
    inputs:
      client:
        description: 'Client identifier'
        required: true
        type: string
 
jobs:
  build:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Determine client traits
        id: client-traits
        run: |
          CLIENT_CONFIG=$(cat clients/${{ github.event.inputs.client }}.json)
          TRAITS=$(echo $CLIENT_CONFIG | jq -r '.traits | join(",")')
          echo "traits=$TRAITS" >> $GITHUB_OUTPUT
      
      - name: Build for ${{ github.event.inputs.client }}
        run: swift build --traits ${{ steps.client-traits.outputs.traits }} -c release
      
      - name: Package application
        run: ./package.sh ${{ github.event.inputs.client }}
      
      - name: Upload to client portal
        run: ./upload.sh ${{ github.event.inputs.client }}
Здесь для каждого клиента мы храним конфигурационный файл JSON с указанием требуемых трейтов, и CI/CD система автоматически собирает и развертывает приложение с нужной функциональностью.

Подводя итоги, могу сказать, что интеграция Package Traits с системами CI/CD открывает огромные возможности для автоматизации процессов разработки, тестирования и развертывания приложений. Это позволяет создавать более гибкие, надежные и эффективные пайплайны, которые могут адаптироваться к различным требованиям и средам. В следующем разделе мы рассмотрим, как Package Traits влияют на архитектуру крупных проектов и какие паттерны проектирования особенно хорошо работают с этой технологией.

Необходимость Swift для не очень опытного разработчика
Всем привет! Возможно, мой вопрос покажется надуманным, но меня это постоянно пилит, хочу...

Восклицательный знак в Swift
Всем привет! Начал опыты со Swift, и тут же столкнулся с модификаторами ? и ! (назову их так)...

Аналог [object class] в Swift
Всем добрый день. Наконец-то дошли руки до знакомства с RESTKit, и решил сразу попробовать это...

Потоки в Swift
В общем, решил поковырять свифт на выходных и выяснил, что не могу нормально создавать потоки. То...

Массив Swift
Есть кусок кода Swift в Xcode: var pageData = NSArray() override init() { ...

Swift compiler error Command failed due to signal: Bus error: 10
Mavericks 10.9.5, VMWare 10.0.3, xCode 6.0.1 (вообще перепробовал все выпуски, в том числе и 6.1...

Учить ли Objective-C новичку или сразу Swift?
Хочу начать изучать программирование под iOS есть ли смысл учить старый Objective-C или можно сразу...

2D Движок для написания игры на SWIFT
Доброго времени суток, программисты! Проблема тут у меня. Подскажите какой оптимальный 2D движок...

События в Cocoa Swift
У меня нет совершенно никакого опыта в написании приложений под мак или айфон, но сейчас...

Цикл for / массив в языке Swift
Я толко начала изучать Swift и при написания простого приложения "Генератор случайных чисел"...

VK SDK swift
Подскажите пожалуйста, как можно подключить VK SDK к проекту на swift. Легко ли это вообще сделать...

Input/output в swift
Начал изучать swift и столкнулся с проблемой ввода значений с клавиатуры. Много чего облазил, но...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru