Правильно подключаем тяжелые зависимости к Xcode проекту через Swift Package Manager

В этой статье я расскажу о проблемах с которыми я столкнулся при подключении тяжелых зависимостей к iOS проекту с помощью Swift Package Manager и о способе их решения.
Тяжелые зависимости
Для начала давайте определим понятие тяжелой зависимости. Под тяжелой зависимостью я понимаю такую зависимость в которой есть большое кол-во исходного кода. Самый распространённый пример такой зависимости — Firebase. Это набор сервисов от Google которые используются при разработке веб и мобильных приложений. Самые часто используемые сервисы это Firebase Crashlytics — для сбора крэшей и их анализа и Firebase Analytics — для продуктовой аналитики.

Firebase содержит в основном Objective-C и С++ код. Но имеется также и Swift и Objective-C++ код.
Проанализировав исходный код с помощью утилиты cloc , можно обнаружить что там 4 803 файла, 150 000 строчек Objective-C кода и 93 000 строчек С++ кода.
Ещё один пример тяжелой зависимости — AWS Swift SDK Это зависимость для работы с сервисами AWS с помощью Swift. Содержит Swift код, около 2 700 файлов и 3 миллиона строчек Swift кода.
Какие проблемы возникают при подключении тяжелых зависимостей через SPM
Если подключать тяжелые зависимости с помощью Swift Package Manager к Xcode проекту, то возникнут следующие проблемы:
1. Время холодной сборки проекта сильно увеличится
Если подключать тяжелую зависимость стандартным способом, через Swift Package Manager, то к проекту подключаются исходники выбранных библиотек. Из-за этого время сборки приложения сильно увеличится так как необходимо будет компилировать эти файлы.
Пример 1 — Очень простое приложение
Возьмем для примера простейшее приложение без тяжелых зависимостей.

После вызова Clean Build и Build With Timing Summary получаем время холодной сборки 0.4 сек.
Далее подключим тяжелую зависимость, например AWS SDK for iOS. Так как AWS SDK состоит из множества библиотек, я для примера подключил всего одну из них — AWS DynamoDB — NoSQL базу данных.

После вызова Clean Build и Build With Timing Summary получаем время холодной сборки 19 сек. Это произошло из-за того что в процессе сборки компилируются исходные файлы этой библиотеки.
Пример 2 — Много-модульное приложение среднего размера
Приложение среднего размера с несколькими десятками модулей без тяжелых зависимостей собирается за 50 секунд.

Если к этому приложению подключить Firebase Crashlytics, Analytics и Messaging, то оно станет собираться почти на 10 секунд дольше.

Итог
При подключении тяжелой зависимости через SPM холодное время сборки приложения увеличивается. Это приводит к тому что на CI приложение будет дольше собираться перед прохождением тестов. Разработчики будут дольше ждать проверок на CI.
2. Время индексации сильно увеличится
Из-за большого количества исходных файлов в тяжелых зависимостях, написанных на разных языках, время на индексацию проекта в Xcode сильно увеличится.
Чтобы понять какие файлы индексирует Xcode надо получить и изучить логи индексации. Для этого достаточно запустить Xcode следующим образом с помощью Terminal:
SOURCEKIT_LOGGING=3 /Applications/Xcode.app/Contents/MacOS/Xcode &> ~/Desktop/indexing.log
Либо вызвать команду:
defaults write com.apple.dt.Xcode IDEIndexShowLog -bool YES

Эта команда позволяет увидеть логи индексации прямо в Xcode, в панели Report Navigator.
Изучив логи можно увидеть что Xcode индексирует абсолютно все исходники которые есть в подключаемой зависимости. Если она разделена на сотни отдельных библиотек, а подключаем к проекту только одну, то Xcode все-равно будет индексировать исходники всех сотен библиотек.
Пример 1 — Очень простое приложение
Без тяжелых зависимостей индексация всего проекта занимает 12 сек. С подключенной тяжелой зависимостью AWS SDK for iOS индексация всего проекта занимает 7 мин 40 сек.
Пример 2 — Много-модульное приложение среднего размера
Приложение среднего размера с несколькими десятками модулей без тяжелых зависимостей индексируется 2 мин 15 секунд.
Если к этому же приложению подключить Firebase Crashlytics, Analytics и Messaging, то оно станет индексироваться 4 мин 20 сек.
Итог
Так как тяжелые зависимости подключаются как исходники, то Xcode их полностью индексирует. Если на CI это не так важно т.к. индексация происходит в процессе сборки, то на компьютерах разработчиков это серьезная проблема. Нормально писать код можно только после окончания индексации.
3. Баги в Xcode при использовании SPM
В Xcode есть серьезный баг, если к проекту подключены Swift пакеты. Если переключаться между ветками, что разработчики делают часто, то он начнет делать Resolving Package Graph, индексацию и Preparing Editor Functionality. И это несмотря на то что Package.resolved файл не изменился. А теперь самое главное: если к проекту подключены тяжелые зависимости, то сразу после переключения на другую ветку, Xcode начинает нагружать все ядра процессора и несколько десятков секунд, а то и минут невозможно ничего делать с проектом. На эту проблему многие жаловались в Twitter (раз, два, три, четыре) и на форуме Apple (раз, два).
Пример:
Переходим с одной ветки на другую, в которой добавились 2 новых Swift файла, а 8 других изменились.
Смотрим что происходит с Xcode:

Сначала Xcode делает Resolving Package Graph, потом индексацию и в конце Preparing Editor Functionality. В это время сначала нагружается одно ядро процессора на 100%, а потом все 8 на M1. А теперь о причинах. В папке DerivedData есть папка SymbolCache в которой расположен файл project.plist. В этом файле хранится информация обо всех символах в проекте. Этот файл для простого iOS проекта с подключенной тяжелой библиотекой у меня весит 141 Мб. Это чрезвычайно много. При переключении ветки, Xcode, по непонятной причине, начинает удалять из этого файла элементы что отнимает очень много времени. Во время удаления элементов из этого файла, Xcode полностью нагружает главный поток что приводит к его полному зависанию. Появляется так называемый Spinning Wheel. Ниже скриншот из Xcode Instruments → Time Profiler, где видно все проблемы о которых я сказал в этом абзаце.

Но, самое неприятное это то что после этого Xcode нагружает все 8 ядер на M1 маке что приводит к зависанию не только Xcode но и всего компьютера.
Из профайлера сложно понять что конкретно делает Xcode. Но из названий можно понять что он продолжает работать с SymbolCache который чрезвычайно тяжелый.
Итог
Xcode содержит серьёзные баги которые мешают пользоваться Xcode в случае проекта с подключенными тяжелыми библиотеками.
4. Отображение списка зависимостей
Тяжелые зависимости, как правило, разделены на большое кол-во библиотек. Кроме того, при попытке подключить одну библиотеку, например Analytics из Firebase, подключается 12 других. Или, например, если подключить AWS DynamoDB из AWS, то подключается 6 разных библиотек.


Здесь я вижу небольшую проблему в том что дополнительных зависимостей много и они засоряют список зависимостей отображаемых в Xcode.
Решение всех проблем
Чтобы решить все проблемы возникающие при подключении тяжелых зависимостей через SPM достаточно просто отказаться от их подключения в виде исходников. Их надо подключать в виде скомпилированных статических библиотек. Тогда их не нужно будет ни компилировать ни индексировать. И Firebase, и AWS, и возможно другие тяжелые зависимости для каждого релиза добавляют XCFramework файлы которые легко подключить к проекту. Даже если XCFramework файла нет его можно собрать самому.
Пример. XCFramework файлы для Firebase можно скачать с GitHub на странице с релизами — https://github.com/firebase/firebase-ios-sdk/releases. Они лежат в файле Firebase.zip.
Чтобы было удобнее подключать и обновлять XCFramework файлы их можно обернуть в Swift пакет. Об этом подробно написано в документации от Apple:
Distributing Binary Frameworks as Swift Packages
Далее я покажу на примере Firebase Crashlytics и Analytics как создать Swift Package с бинарной зависимостью.
Создаём новый локальный Swift пакет и подключаем его к проекту.


Переносим в корневую директорию этого пакета необходимые XCFramework файлы. Например, если мы хотим подключить Crashyltics и Analytics к проекту, то нужно перенести XCFramework файлы из директорий FirebaseCrashlytics и FirebaseAnalytics.

В Package.swift файле в разделе targets удаляем весь код и добавляем ссылки на XCFramework файлы используя .binaryTarget . Далее указываем эти бинарные таргеты у библиотеки которую будем распространять. В итоге Package.swift файл будет выглядеть так:
// swift-tools-version: 5.6 import PackageDescription let package = Package( name: "FirebaseBinaries", platforms: [ .iOS(.v14) ], products: [ .library( name: "FirebaseBinaries", targets: [ "FirebaseAnalytics", "FirebaseCore", "FirebaseCoreDiagnostics", "FirebaseInstallations", "GoogleAppMeasurement", "GoogleDataTransport", "GoogleUtilities", "nanopb", "PromisesObjC", "FirebaseCrashlytics" ]) ], targets: [ .binaryTarget(name: "FirebaseAnalytics", path: "Frameworks/FirebaseAnalytics/FirebaseAnalytics.xcframework"), .binaryTarget(name: "FirebaseCore", path: "Frameworks/FirebaseAnalytics/FirebaseCore.xcframework"), .binaryTarget(name: "FirebaseCoreDiagnostics", path: "Frameworks/FirebaseAnalytics/FirebaseCoreDiagnostics.xcframework"), .binaryTarget(name: "FirebaseInstallations", path: "Frameworks/FirebaseAnalytics/FirebaseInstallations.xcframework"), .binaryTarget(name: "GoogleAppMeasurement", path: "Frameworks/FirebaseAnalytics/GoogleAppMeasurement.xcframework"), .binaryTarget(name: "GoogleDataTransport", path: "Frameworks/FirebaseAnalytics/GoogleDataTransport.xcframework"), .binaryTarget(name: "GoogleUtilities", path: "Frameworks/FirebaseAnalytics/GoogleUtilities.xcframework"), .binaryTarget(name: "nanopb", path: "Frameworks/FirebaseAnalytics/nanopb.xcframework"), .binaryTarget(name: "PromisesObjC", path: "Frameworks/FirebaseAnalytics/PromisesObjC.xcframework"), .binaryTarget(name: "FirebaseCrashlytics", path: "Frameworks/FirebaseCrashlytics/FirebaseCrashlytics.xcframework") ] )
Далее необходимо подключить к главному таргету библиотеку FirebaseBinaries . Выбираем таргет, и в разделе Frameworks, Libraries and Embedded Content нажимаем на + и выбираем FirebaseBinaries .

Кроме того, так как Firebase частично написан на C++, нужно подключить библиотеку libc++.tbd .

В файле Firebase.zip в README.md так же указано что нужно добавить флаг линковки
-ObjC

Все! Теперь можно использовать Crashlytics и Analytics в проекте.
Если не хотите держать Swift пакет с xcframework файлами рядом с проектом, то можете создать отдельный репозиторий, перенести туда этот пакет и подключить его к основному проекту указав адрес репозитория.
Результат
Время сборки простейшего приложения, после подключения тяжелой библиотеки, почти не изменилось. Было 0.4 сек стало 1 сек.

Время на индексацию никак не изменилось т.к. бинарная зависимость не индексируется.
Xcode больше не нагружает процессор при переключении веток.
Кол-во зависимостей увеличилось всего на одну — FirebaseBinaries.
Удовлетворенность разработчиков выросла. Больше никто не жалуется на то что Xcode тормозит, больно переключать ветки.
Минусы
Минус такого подхода один — станет сложнее обновлять зависимости. Необходимо скачивать zip файл с xcframework файлами и помещать их в Swift Package. К счастью это можно автоматизировать.
Вывод
Если вы хотите подключить к iOS проекту, через Swift Package Manager, зависимость содержащую огромное количество исходного кода, подключите её в скомпилированном виде, через XCFramework файл. Благодаря этому, скорость сборки вашего приложения не изменится, как и время на индексацию исходного кода, а самое главное, вы избежите багов Xcode при работе с SPM.
Создание Swift проекта с SPM (Swift Package Manager) Xcode
Swift Package Manager (SPM) – это, по сути конфиг, вашего проекта. Используя SPM у Вас нет необходимости использовать xcodeproj для работы с проектом, что в свою очередь позволяет запускать проекты не только на Mac, но и на Linux, где работать с xcodeproj на текущий момент невозможно.
Чтобы всё получилось необходимо установить Xcode, без этого никак. Также нужно базовое понимание команд, используемых в терминале (terminal).
Создание
Чтобы создать Swift проект с последующим использованием Swift Package Manager откройте терминал. Создайте директорию в нужном Вам месте. Название директории имеет значение, как назовёте директорию так и будет называться проект. Для удобства назовём директорию SpmProject.
Создание директории
mkdir SpmProject
Переход в директорию
cd SpmProject
Создание проекта с SPM
swift package init — type executable
Терминал уведомит, что был добавлен ряд файлов. Например, так как показан на скрине ниже.
Теперь интересует главное – как добавить зависимость? Открываем созданный файл «Package.swift» в любом редакторе (например, в Xcode).
Данный файл хранит в себе конфигурацию проекта, таргеты, зависимости. Добавим в качестве зависимости библиотеку Alamofire. Для этого в зависимости (dependencies) проекта нужно добавить ссылку откуда брать библиотеку. Затем, в конкретный таргет прописать зависимость. Выглядеть будет примерно следующим образом.
Если использовать в качестве редактора данного файла Xcode, то он сам подтянет зависимость. В противном случае для скачивания библиотеки потребуется выполнить команду в терминале.
Как в Swift Package Manager сэкономить гигабайты трафика и места на диске
В этой статье я покажу, как использовать SPM для хранения зависимостей в репозитории и реализовать это лучше, чем в CocoaPods.
6 месяцев назад

Многие из вас сталкивались с неприятной ситуацией — открываешь проект или переключаешь ветку, и видишь печальную картину того, как SPM ресолвит пакеты.
Одним из преимуществ CocoaPods по сравнению с SPM является то, что проверки зависимостей хранятся вместе с проектом непосредственно в репозитории. Это позволяет безболезненно запускать проект из любого коммита и не тратить время на CI для загрузки зависимостей и их разрешения.
В этой статье я покажу, как использовать SPM для хранения зависимостей в репозитории и реализовать это лучше, чем в CocoaPods.
Прежде чем начать, давайте определим список требований к будущему решению:
- Мы продолжаем жить в парадигме пакетов Swift
- Внешние зависимости пакетов становятся локальными
- Требуется механизм локального клонирования внешних зависимостей
- Храним локально только те файлы из репозиториев зависимостей, которые необходимы проекту
Получив такую конфигурацию, мы можем приступать к реализации.
Клонирование зависимостей
Начнем с клонирования зависимостей. Для этого создадим отдельный локальный пакет и добавим все необходимые зависимости в Package.swift:
// swift-tools-version: 5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "VendorPackages", platforms: [ .iOS(.v15), .macOS(.v12), ], products: [ .library( name: "VendorPackages", targets: ["VendorPackages"] ), ], dependencies: [ .package( url: "https://github.com/devicekit/DeviceKit.git", exact: "5.0.0" ), .package( url: "https://github.com/kean/Nuke.git", exact: "12.1.2" ), .package( url: "https://github.com/groue/GRDB.swift.git", exact: "6.15.1" ), .package( url: "https://github.com/SnapKit/SnapKit", exact: "5.6.0" ), .package( url: "https://github.com/airbnb/lottie-ios", exact: "4.2.0" ), ], targets: [ .target( name: "VendorPackages", dependencies: [ "DeviceKit", .product(name: "Nuke", package: "Nuke"), .product(name: "NukeUI", package: "Nuke"), .product(name: "NukeExtensions", package: "Nuke"), "SnapKit", .product(name: "GRDB", package: "GRDB.swift"), .product(name: "Lottie", package: "lottie-ios"), ] ), ] )
Этот пакет не содержит никакого кода. Его единственная ценность — манифест зависимостей.
Открыв пакет в Xcode или выполнив команду swift package resolve, можно заметить, что в каталоге пакета появился скрытый каталог .build с подкаталогом checkouts, содержащим клоны локальных зависимостей:

Обратим внимание на размер каталога .build. Он равен 993 МБ, из которых 328 МБ занимают checkouts и 665 МБ — repositories. Я специально выбрал популярные пакеты (GRDB, Lottie) с длительной историей, чтобы продемонстрировать масштаб проблемы.
Минимальные файлы
Зная, где находятся клоны локальных зависимостей, следующим шагом будет определение минимального количества файлов из этих пакетов, необходимых нашему проекту для сборки. Такими файлами, очевидно, являются целевые исходники тех продуктов, которые используются в нашем исходном локальном пакете, в котором мы указали необходимые внешние зависимости.
Нас совершенно не интересуют ни тестовые target файлы, ни различные демонстрационные ресурсы. Иными словами, все то, что просто занимает место на диске и не используется при компиляции проекта. При необходимости эти «лишние» файлы всегда можно использовать отдельно.
Определение этого минимума файлов на первый взгляд может показаться нетривиальной задачей, но на практике SPM CLI предоставляет нам все необходимое:
swift package describe --type=json
Используем эту команду в каталоге, где находится, например, пакет Nuke:
< "dependencies" : [ ], "manifest_display_name" : "Nuke", "name" : "Nuke", "path" : ".build/checkouts/Nuke", "platforms" : [. ], "products" : [. ], "targets" : [ < "c99name" : "NukeVideo", "module_type" : "SwiftTarget", "name" : "NukeVideo", "path" : "Sources/NukeVideo", "product_memberships" : [ "NukeVideo" ], "sources" : [ "AVDataAsset.swift", "ImageDecoders+Video.swift", "VideoPlayerView.swift" ], "target_dependencies" : [ "Nuke" ], "type" : "library" >, < "c99name" : "NukeUI", "module_type" : "SwiftTarget", "name" : "NukeUI", "path" : "Sources/NukeUI", "product_memberships" : [ "NukeUI" ], "sources" : [ "FetchImage.swift", "Internal.swift", "LazyImage.swift", "LazyImageState.swift", "LazyImageView.swift" ], "target_dependencies" : [ "Nuke" ], "type" : "library" >, < "c99name" : "NukeExtensions", "module_type" : "SwiftTarget", "name" : "NukeExtensions", "path" : "Sources/NukeExtensions", "product_memberships" : [ "NukeExtensions" ], "sources" : [ "ImageLoadingOptions.swift", "ImageViewExtensions.swift" ], "target_dependencies" : [ "Nuke" ], "type" : "library" >, < "c99name" : "Nuke", "module_type" : "SwiftTarget", "name" : "Nuke", "path" : "Sources/Nuke", "product_memberships" : [ "Nuke", "NukeUI", "NukeVideo", "NukeExtensions" ], "sources" : [/* a lot of sources here */], "type" : "library" >], "tools_version" : "5.6" >
JSON-вывод содержит массив целей, в котором буквально находятся все необходимые исходные файлы, а также продукты, включающие эти цели (если таргет содержит ресурсы, то они будут находиться по ключу «resources»).
Вот! 100% то, что нам нужно.
Копируем файлы
Поскольку самая сложная и нетривиальная часть оказалась тривиальной и решается одной командой, то на данном этапе остается только написать небольшой скрипт, который будет копировать необходимые файлы для продуктов, указанных в нашем локальном проекте, с сохранением структуры каталогов.
Я сделал это с помощью простого shell-скрипта и утилиты jq. Вы можете использовать привычные для вас инструменты, опираясь на мою реализацию в качестве образца:
rm -rf Sources && mkdir -p Sources cd _Proxy # directory for local package with remote dependencies swift package clean swift package update required_products=$(swift package describe --type json | jq -c '(.targets[] | select(.name=="VendorPackages")) | .product_dependencies[]') for repo in $(ls .build/checkouts); do echo $repo mkdir -p ../Sources/$repo cp -r .build/checkouts/$repo/Package.swift ../Sources/$repo/_Package.swift package_json=$(swift package --package-path .build/checkouts/$repo describe --type json | jq -c) targets=$(jq -c '(.targets[] | select(.product_memberships != null))'С помощью этого скрипта мы скопировали все файлы, необходимые для сборки нашего проекта, из всех внешних зависимостей.
Используем их
Последний вопрос, на который необходимо ответить, — как на самом деле использовать скопированные файлы в нашем проекте.
Здесь нет единого ответа. Например, можно развернуть скрипт и скопировать манифесты пакетов вместе с другими файлами, каким-то образом удалив оттуда неиспользуемые цели: с помощью регулярных выражений, модификаций AST и т.д.
Я решил не автоматизировать этот шаг и добавил еще один локальный пакет, в котором зарегистрировал все зависимости — указав локальные пути к скопированным файлам:
// swift-tools-version: 5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "VendorPackages", platforms: [ .iOS(.v15), .macOS(.v12), ], products: [ .library(name: "DeviceKit", targets: ["DeviceKit"]), .library(name: "GRDB", targets: ["GRDB"]), .library(name: "Lottie", targets: ["Lottie"]), .library(name: "Nuke", targets: ["Nuke"]), .library(name: "NukeUI", targets: ["NukeUI"]), .library(name: "NukeExtensions", targets: ["NukeExtensions"]), .library(name: "SnapKit", targets: ["SnapKit"]), ], targets: [ .target( name: "DeviceKit", path: "Sources/DeviceKit/Source" ), .target( name: "Nuke", path: "Sources/Nuke/Sources/Nuke" ), .target( name: "NukeUI", dependencies: ["Nuke"], path: "Sources/Nuke/Sources/NukeUI" ), .target( name: "NukeExtensions", dependencies: ["Nuke"], path: "Sources/Nuke/Sources/NukeExtensions" ), .target( name: "GRDB", dependencies: ["CSQLite"], path: "Sources/GRDB.swift/GRDB" ), .systemLibrary( name: "CSQLite", path: "Sources/GRDB.swift/Sources/CSQLite" ), .target( name: "Lottie", path: "Sources/lottie-ios/Sources" ), .target( name: "SnapKit", path: "Sources/SnapKit/Sources" ), ], swiftLanguageVersions: [.v5] )Преимущество такого подхода заключается в том, что вы контролируете описание целей, их настройки и т.д.
Таким образом, мы автоматизировали гранулярное копирование необходимых файлов зависимостей, но оставили под ручным контролем локальный манифест пакета, который будет использоваться в проекте.
В 211 раз меньше
Невероятно, но факт — размер скопированных файлов составляет 4.7 МБ — это в 211 раз меньше, чем наш каталог .build.
Исходный код из этой статьи можно найти в этом репозитории.
Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Swift Package Manager
Добрый день. Не судите строго за мою первую статью)
Надеюсь, кому-то пригодятся мои мысли и разбор как мне кажется интересной темы по ios разработке.Каждый начинающий разработчик сталкивается с тем, что ему надо втащить в проект какую-то зависимость, для того чтобы сэкономить время на написание кода, и разработку приложения. До недавнего времени на троне восседали два короля, это CocoaPods и Carthage. CocoaPods по праву занимает первое место среди менеджеров зависимостей. Ему 100500 лет, огромное комьюнити, которое позволяет легко решить практически любую проблему, затуп или ошибку в проекте при сборке того, или иного билда. На самом деле плюсов у него очень много, и перечислять их долго, да и не тема эта моей статьи. Второй игрок на троне, это Carthage, это менеджер зависимостей как по мне лучше чем CocoaPods. У него зависимости собираются один раз, не создается дурацкий .xcworkspace и куча мусора в проекте, репозиторий берет с githab и еще ряд преимуществ. Из минусов, не все фреймворки поддерживают Carthage, очень много надо делать ручками, да и пожалуй все.
И вот недавно apple выкатила последнюю версию своего нативного инструмента под названием Swift Package Manager. Это свой собственный менеджер зависимостей для сборок сторонних фреймворков. Сегодня покажу вам, как установить свою первую зависимость с помощью него. Погнали )))
Открываем XCode, создаем новый проект и в главном меню выбираем File->Add Package Dependency. Откроется меню, в которое мы будем добавлять наши зависимости.
В этом окошке нужно вставить url ссылку, которая ведет на нашу библиотеку. Для примера я возьму популярную библиотеку RxSwift. Заходим на сайт, копируем ссылку. В моем примере она будет выглядеть так “https://github.com/ReactiveX/RxSwift" и нажимаем Next.
Откроется меню, где указан наш репозиторий и можно выбрать версию, ветку и комит. Выбираем в версии мажор и жмакаем Next.
Дальше покажется окно выбора пакетов. Отмечаем те, какие именно вам нужны. В этом примере я использую RxSwift и RxCocoa. Нажимаем кнопку Finish.
В результате мы видим, что библиотечка поставилась. И ей можно пользоваться. Все просто и удобно.
И если хотите удалить лишнюю библиотеку, или не актуальную, просто дропните ее из пакета в Project как на скрине, и Xcode удалит ее из проекта.
Как видно, Swift Package Manager облегчает нам работу со сторонними библиотеками. Его смело можно использовать в домашних и маленьких проектах. У нас нет лишних файлов, не надо что-то прописывать руками и тд.
Надеюсь статья оказалась полезна, не ругайтесь сильно на грамотность и написание, я буду стараться каждый раз писать лучше. Если вы новичок, присоединяйтесь к нашему сообществу.
Всем спасибо за внимание.