Предыстория и сразу к делу
В свое время мне понадобилось обернуть написанный модуль в библиотеку. Порядочно погуглив, я нашел кучу туториалов, суть которых - создается библиотека с одним-двумя .swift - файлами. У меня же был целый проект, да еще с подами (а ля Alamofire, Moya, EasyPeasy и др), и создание библиотеки именно этим и усложнялось, было непонятно как переносить поды, нужно ли их вообще переносить и как в целом правильно сбилдить такую библиотеку.
P.S. Данный туториал не претендует на полноту теории, скорее он из раздела "как сделать правильно и чтоб работало".
P.P.S. Статья будет написана как расширение обычных туториалов для случая использования в библиотеке Static Library - cocoapods, но ее можно использовать и как просто туториал для создания Static Library.
Как создать фреймворк Universal Framework с использованием Cocoapods я писал здесь
Тестировалось на xcode 12.4, swift 5
1. Билдим библиотеку
Итак, начинаем, cоздаем новый проект в xcode:

Выбираем Static Library, жмем Next,
библиотеку назовем StaticLibraryExample - рекомендую давать название без пробелов!
Получаем пустую библиотеку с одним автоматически созданным файлом StaticLibraryExample.swift:

Теперь мы можем использовать созданный файл(или удалить его), а также создать бесконечно своих файлов и добавить их в библиотеку.
Не забываем указывать модификаторы доступа public и open для классов свойств и функций!
Шрифты, локализацию Localizable.strings, картинки Assets не вносим в библиотеку, их добавим в клиентский проект отдельно!
Если же у нас имеется проект, который нужно сделать подмодулем(как было в моем случае), то берем все необходимые файлы и копируем их из этого проекта в нашу библиотеку:

Получаем что то наподобие(автоматический созданный файл StaticLibraryExample я удалил):

Теперь попробуем сбилдить нашу библиотеку (неважно на симуляторе или устройстве), нажимаем Ctrl+B. Если в вашей библиотеке нет cocoapods зависимостей то все компилируется успешно - билдим проект на устройстве и симуляторе и переходим к пункту 2 туториала.
Если же есть, то вы получите ошибку наподобие этой:

Окей, создаем новый Podfile с необходимыми подами, например мне нужны были следующие(какие поды дело несущественное):
platform :ios, '13.0' target 'Static Library Example' do pod 'Moya' pod 'Alamofire' pod 'Kingfisher' pod 'EasyPeasy' pod 'KeychainAccess' pod 'SwiftPhoneNumberFormatter' end
Устанавливаем поды - pod install. Открываем созданный workspace (на установке подов я не останавливаюсь).
Билдим библиотеку для симулятора и устройства, все должно быть успешно, если есть ошибки рекомендую в Targets -> StaticLibraryExample -> Build phases в разделе Library search paths удалить все, кроме $(inherited).
После того как все билдится успешно, файл библиотеки перестанет подсвечиваться красным, и, нажав на него правой кнопкой и выбрав Show In Finder, мы можем найти его на диске (один файл для iphoneos и второй для iphonesimulator):


В итоге на первом этапе для нас главное получить эти два файла библиотеки - один для айфона, второй - для симулятора.

2. Компилируем Universal Static Library
Под Universal Static Library имеется в виду библиотека (файл), который подходит и под устройство и под симулятор.
Сразу отмечу, что ее можно создать через терминал, такой способ есть во многих туториалах, однако создавать через агрегатор намного удобнее, к тому же если вы что что измените в библиотеке и вам необходимо будет ее пересобрать, то будет достаточно сбилдить агрегатор еще раз.
Добавляем новый таргет - Aggregator:


Жмем Next, назовем агрегатор UniversalLib_Aggregator, жмем Готово:

Далее добавим Run Script Phase - код, который будет выполняться при билде этого агрегатора:

Удаляем все что там написано:

И вставляем код, который я нашел на просторах интернета. Этот код универсален для любой вашей будущей библиотеки, только в переменную LIB_NAME необходимо вписывать имя проекта(библиотеки):
# 1: Declare variables # Вписываем имя библиотеки: LIB_NAME="StaticLibraryExample" RESULT_DIR="libUniversal" BUILD_DIR_SIMULATOR="Debug-iphonesimulator" BUILD_DIR_DEVICE="Debug-iphoneos" LIB_BINARY_NAME="lib$LIB_NAME.a" LIB_BINARY_NAME_SIMULATOR="lib$LIB_NAME-simulator.a" LIB_BINARY_NAME_DEVICE="lib$LIB_NAME-device.a" SWIFTMODULE_DIR=$LIB_NAME".swiftmodule" # 2: Билд # Билдим для симулятора xcodebuild -target $LIB_NAME -configuration ${CONFIGURATION} -sdk iphonesimulator -arch x86_64 BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" # Билдим для устройства xcodebuild -target $LIB_NAME ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" # 3: Операции с бинарными файлами # Переходим в билд директорию cd $BUILD_DIR # Удаляем результат предыдущей сборки rm -rf $BUILD_DIR/$RESULT_DIR 2> /dev/null # Создаем новую директорию для библиотеки mkdir $RESULT_DIR # Копируем двоичный файл симулятора # в директорию библиотеки и переименовываем его cp ./$BUILD_DIR_SIMULATOR/$LIB_BINARY_NAME ./$RESULT_DIR/$LIB_BINARY_NAME_SIMULATOR # Копируем двоичный файл устройства # в директорию библиотеки и переименовываем его cp ./$BUILD_DIR_DEVICE/$LIB_BINARY_NAME ./$RESULT_DIR/$LIB_BINARY_NAME_DEVICE # Создаем нашу universal библиотеку(второе название "fat") lipo -create ./$RESULT_DIR/$LIB_BINARY_NAME_SIMULATOR ./$RESULT_DIR/$LIB_BINARY_NAME_DEVICE -output ./$RESULT_DIR/$LIB_BINARY_NAME # Удаляем двоичные файлы симулятора и устройства: rm ./$RESULT_DIR/$LIB_BINARY_NAME_SIMULATOR rm ./$RESULT_DIR/$LIB_BINARY_NAME_DEVICE # 4: Создаем .swiftmodule # # Создаем директорию mkdir $RESULT_DIR/$SWIFTMODULE_DIR # Копируем 'swiftmodule' симулятора в созданную директорию cp -r $BUILD_DIR_SIMULATOR/$SWIFTMODULE_DIR $RESULT_DIR # Копируем 'swiftmodule' устройства в созданную директорию cp -r $BUILD_DIR_DEVICE/$SWIFTMODULE_DIR/* $RESULT_DIR/$SWIFTMODULE_DIR # Удаляем билд директорию rm -rf $PROJECT_DIR/build
Билдим наш агрегатор Ctrl+B:

После успешного билда открываем файл библиотеки:

Да, открывается наш старый файл библиотеки для устройства из папки Debug-iphoneos, нам нужно перейти на уровень вверх (например щелкнув два раза на Products):

Перейдя в Products, мы видим нашу Universal Library:

В итоге на этом этапе мы получили нашу Universal Library, все файлы которой находятся в папке libUniversal (Обратите внимание что имя универсальной библиотеки тоже задается в скрипте переменной RESULT_DIR).
3. Интегрируем Static Library в ClientApp
Под "ClientApp" имеется в виду любой проект
Итак, осталось самое простое - внедряем библиотеку в проект.
Создаем новый xcode проект, выбираем App, жмем Next, назовем проект ClientApp


Переносим нашу папку libUniversal в наш проект:

Далее добавляем файл библиотеки libStaticLibraryExample.a в проект:



Должно получиться так:

Далее нам нужно заполнить раздел Import Paths:

Переходим во ViewController.swift и импортируем нашу библиотеку:

Билдим проект Ctrl+B, если вы не используете cocoapods, то все должно сбилдиться и нашу библиотеку можно использовать! (переходите к пункту 4. Тестирование)
Если же используем поды то необходимо в ClientApp установить те же поды, что были у нас при компилировании библиотеки. Также я столкнулся с такой ошибкой:

Решается так: Чистим проект Product -> Clean Build Folder, затем открываем Terminal (необязательно по пути где наш проект), вставляем следующий код(чистим папку DerivedData):
rm -rf ~/Library/Developer/Xcode/DerivedData
Затем билдим проект снова, получаем закономерную ошибку, что наши поды не обнаружены:

Устанавливаем Pods - код для Podfile берем из Static Library (см. выше):

Открываем ClientApp.xcworkspace, и билдим проект - убеждаемся, что все успешно!
Не забудьте перенести шрифты(а также добавить данные о них в Info.plist), файлы локализации Localizable.strings, а также добавить картинки в Assets! Т.е. все те файлы которые вам не нужны в вашей библиотеке, но нужны для запуска ClientApp.
4. Тестируем
В моей библиотеке Static Library Example был такой класс:
public class TestViewController: UIViewController { public override func viewDidLoad() { super.viewDidLoad() } public func printLog() { print("The Universal Library works!") } }
Как видите это простой класс для тестирования работоспособности библиотеки, который содержит в себе публичную функцию printLog().
Давайте вызовем эту функцию из нашего ClientApp:

Отлично! Библиотека работает, вы можете использовать классы, функции и свойства доступных файлов библиотеки!
